在 Vue 3 中测试工作者线程
1. Vue 3 项目中快速启动测试
方法一:使用 Vue CLI 或 Vite 创建项目
bash
bash
# 使用 Vite 创建 Vue 3 项目
npm create vue@latest worker-test
# 或
npm init vue@latest
# 选择配置:
# ✔ Project name: worker-test
# ✔ TypeScript: No
# ✔ JSX Support: No
# ✔ Vue Router: No
# ✔ Pinia: No
# ✔ Vitest: No
# ✔ End-to-End Testing: No
# ✔ ESLint: No
# ✔ Prettier: No
cd worker-test
npm install
npm run dev
方法二:直接创建最小化项目
bash
bash
mkdir vue-worker-test
cd vue-worker-test
# 创建 package.json
cat > package.json << 'EOF'
{
"name": "vue-worker-test",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.4.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.0",
"vite": "^5.0.0"
}
}
EOF
# 安装依赖
npm install
# 创建项目结构
2. Vue 3 项目结构
vue-worker-test/
├── public/
│ └── worker.js # Worker 文件(如果放在 public)
├── src/
│ ├── workers/ # Worker 目录
│ │ └── calculator.worker.js
│ ├── composables/ # 组合式函数
│ │ └── useWorker.js
│ ├── components/
│ │ └── WorkerTest.vue
│ ├── App.vue
│ ├── main.js
│ └── style.css
├── index.html
├── vite.config.js
└── package.json
3. Vue 3 组件中使用 Worker
App.vue
vue
javascript
<template>
<div id="app">
<h1>Vue 3 Web Worker 测试</h1>
<WorkerTest />
</div>
</template>
<script setup>
import WorkerTest from './components/WorkerTest.vue'
</script>
WorkerTest.vue 组件
vue
javascript
<template>
<div class="worker-test">
<div class="controls">
<button @click="startWorker" :disabled="workerRunning">
{{ workerRunning ? 'Worker运行中' : '启动Worker' }}
</button>
<button @click="sendMessage" :disabled="!workerRunning">
发送计算任务
</button>
<button @click="stopWorker" :disabled="!workerRunning">
停止Worker
</button>
<button @click="runInMainThread">
在主线程运行(会阻塞UI)
</button>
<div class="input-group">
<label>计算次数:</label>
<input v-model.number="iterations" type="number" min="1000" max="10000000">
</div>
</div>
<div class="results">
<div class="result-card">
<h3>Worker 线程结果</h3>
<div v-if="workerResult">
<p>计算结果: {{ workerResult.result }}</p>
<p>耗时: {{ workerResult.duration }}ms</p>
<p>状态: {{ workerStatus }}</p>
</div>
<div v-else>
<p>等待 Worker 返回结果...</p>
</div>
</div>
<div class="result-card">
<h3>主线程结果</h3>
<div v-if="mainThreadResult">
<p>计算结果: {{ mainThreadResult.result }}</p>
<p>耗时: {{ mainThreadResult.duration }}ms</p>
<p>UI阻塞: {{ mainThreadResult.blocked ? '是' : '否' }}</p>
</div>
</div>
</div>
<div class="logs">
<h3>日志</h3>
<div class="log-list">
<div v-for="(log, index) in logs" :key="index" class="log-item">
[{{ log.time }}] {{ log.message }}
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onUnmounted } from 'vue'
// 响应式数据
const iterations = ref(1000000)
const workerRunning = ref(false)
const workerResult = ref(null)
const mainThreadResult = ref(null)
const logs = ref([])
const workerStatus = ref('未启动')
// Worker 实例
let worker = null
// 添加日志
const addLog = (message) => {
logs.value.unshift({
time: new Date().toLocaleTimeString(),
message
})
if (logs.value.length > 20) logs.value.pop()
}
// 启动 Worker
const startWorker = () => {
if (typeof Worker === 'undefined') {
addLog('❌ 浏览器不支持 Web Workers')
return
}
try {
// 使用 Vite 的特殊导入方式
worker = new Worker(new URL('../workers/calculator.worker.js', import.meta.url), {
type: 'module' // 如果需要 ES 模块
})
worker.onmessage = (event) => {
workerResult.value = event.data
workerStatus.value = '空闲'
addLog(`✅ Worker 返回结果: ${event.data.result}`)
}
worker.onerror = (error) => {
addLog(`❌ Worker 错误: ${error.message}`)
workerStatus.value = '错误'
}
workerRunning.value = true
workerStatus.value = '等待中'
addLog('🚀 Worker 已启动')
} catch (error) {
addLog(`❌ 创建 Worker 失败: ${error.message}`)
}
}
// 发送消息到 Worker
const sendMessage = () => {
if (worker) {
const data = {
type: 'calculate',
iterations: iterations.value,
timestamp: Date.now()
}
worker.postMessage(data)
workerResult.value = null
workerStatus.value = '计算中'
addLog(`📤 发送计算任务: ${iterations.value} 次迭代`)
}
}
// 停止 Worker
const stopWorker = () => {
if (worker) {
worker.terminate()
worker = null
workerRunning.value = false
workerStatus.value = '已停止'
addLog('🛑 Worker 已停止')
}
}
// 在主线程运行计算(会阻塞 UI)
const runInMainThread = () => {
addLog('⚠️ 在主线程开始计算(UI会卡住)...')
const startTime = performance.now()
// 模拟耗时计算
let result = 0
for (let i = 0; i < iterations.value; i++) {
result += Math.sqrt(i) * Math.sin(i)
// 每 10000 次检查一次,模拟 UI 卡顿
if (i % 10000 === 0) {
// 这会阻塞 UI 更新
mainThreadResult.value = {
result: result.toFixed(2),
duration: (performance.now() - startTime).toFixed(2),
blocked: true
}
}
}
const duration = performance.now() - startTime
mainThreadResult.value = {
result: result.toFixed(2),
duration: duration.toFixed(2),
blocked: true
}
addLog(`⏱️ 主线程计算完成,耗时: ${duration.toFixed(2)}ms`)
}
// 组件卸载时清理
onUnmounted(() => {
if (worker) {
worker.terminate()
}
})
</script>
<style scoped>
.worker-test {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.controls {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 30px;
padding: 20px;
background: #f5f5f5;
border-radius: 8px;
}
.controls button {
padding: 10px 20px;
border: none;
border-radius: 4px;
background: #007bff;
color: white;
cursor: pointer;
transition: background 0.3s;
}
.controls button:hover:not(:disabled) {
background: #0056b3;
}
.controls button:disabled {
background: #ccc;
cursor: not-allowed;
}
.input-group {
display: flex;
align-items: center;
gap: 10px;
}
.input-group input {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
width: 150px;
}
.results {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 30px;
}
.result-card {
padding: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.result-card h3 {
margin-top: 0;
color: #333;
}
.logs {
background: #f8f9fa;
padding: 20px;
border-radius: 8px;
}
.log-list {
max-height: 300px;
overflow-y: auto;
}
.log-item {
padding: 8px 0;
border-bottom: 1px solid #eee;
font-family: 'Courier New', monospace;
font-size: 14px;
}
</style>
4. Worker 文件
src/workers/calculator.worker.js
javascript
javascript
// Web Worker - 执行耗时计算
self.onmessage = function(event) {
const { type, iterations, timestamp } = event.data
if (type === 'calculate') {
const startTime = performance.now()
try {
// 模拟复杂计算
let result = heavyCalculation(iterations)
const duration = performance.now() - startTime
// 发送结果回主线程
self.postMessage({
type: 'result',
iterations,
result,
duration,
timestamp,
workerId: self.name || 'calculator'
})
} catch (error) {
self.postMessage({
type: 'error',
error: error.message,
timestamp
})
}
}
}
// 耗时计算函数
function heavyCalculation(iterations) {
let sum = 0
for (let i = 0; i < iterations; i++) {
sum += Math.sqrt(i) * Math.sin(i) * Math.cos(i)
}
return sum
}
// 支持多种计算类型
function fibonacci(n) {
if (n <= 1) return n
return fibonacci(n - 1) + fibonacci(n - 2)
}
function primeCheck(num) {
if (num <= 1) return false
for (let i = 2; i <= Math.sqrt(num); i++) {
if (num % i === 0) return false
}
return true
}
// 错误处理
self.onerror = function(error) {
console.error('Worker error:', error)
}
5. 使用组合式函数封装 Worker
src/composables/useWorker.js
javascript
javascript
import { ref, onUnmounted } from 'vue'
export function useWorker(workerPath, options = {}) {
const worker = ref(null)
const isRunning = ref(false)
const result = ref(null)
const error = ref(null)
const isLoading = ref(false)
const initWorker = () => {
if (typeof Worker === 'undefined') {
throw new Error('浏览器不支持 Web Workers')
}
try {
worker.value = new Worker(new URL(workerPath, import.meta.url), options)
isRunning.value = true
return true
} catch (err) {
error.value = err.message
return false
}
}
const postMessage = (data) => {
if (!worker.value) return
return new Promise((resolve, reject) => {
isLoading.value = true
const messageHandler = (event) => {
if (event.data.type === 'error') {
error.value = event.data.error
reject(event.data)
} else {
result.value = event.data
resolve(event.data)
}
isLoading.value = false
worker.value.removeEventListener('message', messageHandler)
}
const errorHandler = (err) => {
error.value = err.message
isLoading.value = false
reject(err)
worker.value.removeEventListener('error', errorHandler)
}
worker.value.addEventListener('message', messageHandler)
worker.value.addEventListener('error', errorHandler)
worker.value.postMessage(data)
})
}
const terminate = () => {
if (worker.value) {
worker.value.terminate()
worker.value = null
isRunning.value = false
}
}
// 自动清理
onUnmounted(terminate)
return {
worker,
isRunning,
result,
error,
isLoading,
initWorker,
postMessage,
terminate
}
}
在组件中使用组合式函数
vue
javascript
<template>
<div>
<button @click="calculate">使用组合式函数计算</button>
<div v-if="workerResult">
结果: {{ workerResult.result }}
</div>
</div>
</template>
<script setup>
import { useWorker } from '../composables/useWorker'
const {
isRunning,
result: workerResult,
error,
isLoading,
initWorker,
postMessage,
terminate
} = useWorker('../workers/calculator.worker.js')
// 初始化 Worker
initWorker()
const calculate = async () => {
try {
await postMessage({
type: 'calculate',
iterations: 1000000
})
} catch (err) {
console.error('计算失败:', err)
}
}
</script>
6. Vite 配置(vite.config.js)
javascript
javascript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
// Worker 配置
worker: {
format: 'es', // 使用 ES 模块
plugins: [] // 可以添加 worker 专用插件
},
// 开发服务器配置
server: {
port: 3000,
open: true, // 自动打开浏览器
cors: true
},
// 构建配置
build: {
target: 'esnext',
sourcemap: true
}
})
7. 在 Vue Router 中测试
创建测试页面
vue
javascript
<!-- src/views/WorkerTest.vue -->
<template>
<div class="worker-test-view">
<h1>Web Worker 性能对比</h1>
<PerformanceComparison />
</div>
</template>
<script setup>
import PerformanceComparison from '../components/PerformanceComparison.vue'
</script>
路由配置
javascript
javascript
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import WorkerTest from '../views/WorkerTest.vue'
const routes = [
{
path: '/worker-test',
name: 'WorkerTest',
component: WorkerTest
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
8. 测试不同类型的数据传输
复杂 Worker - 支持多种操作
javascript
javascript
// src/workers/multi-purpose.worker.js
self.onmessage = async function(e) {
const { type, data, id } = e.data
try {
let result
switch (type) {
case 'image-process':
result = await processImage(data)
break
case 'json-parse':
result = JSON.parse(data)
break
case 'heavy-compute':
result = heavyCompute(data)
break
case 'api-fetch':
result = await fetchData(data)
break
default:
throw new Error(`未知的操作类型: ${type}`)
}
self.postMessage({
id,
type,
result,
success: true
})
} catch (error) {
self.postMessage({
id,
type,
error: error.message,
success: false
})
}
}
async function processImage(imageData) {
// 模拟图像处理
return new Promise(resolve => {
setTimeout(() => {
resolve({
processed: true,
size: imageData.length,
preview: 'data:image/png;base64,...'
})
}, 1000)
})
}
function heavyCompute(params) {
let result = 0
for (let i = 0; i < params.iterations; i++) {
result += Math.complexOperation(i)
}
return result
}
async function fetchData(url) {
const response = await fetch(url)
return response.json()
}
9. 快速启动脚本
创建 setup-vue-worker.sh:
bash
bash
#!/bin/bash
echo "🚀 创建 Vue 3 + Web Worker 测试项目..."
# 使用 Vite 创建项目
npm create vue@latest vue-worker-demo -- --typescript --router --pinia
cd vue-worker-demo
# 创建目录结构
mkdir -p src/workers src/composables src/components
# 创建 Worker 文件
cat > src/workers/demo.worker.js << 'EOF'
self.onmessage = function(e) {
console.log('Worker received:', e.data)
// 模拟耗时操作
const start = performance.now()
let result = 0
for (let i = 0; i < e.data.iterations; i++) {
result += Math.sqrt(i)
}
const duration = performance.now() - start
self.postMessage({
result: result.toFixed(2),
duration: duration.toFixed(2),
input: e.data
})
}
EOF
# 创建测试组件
cat > src/components/WorkerDemo.vue << 'EOF'
<template>
<div class="demo">
<h2>Vue 3 + Web Worker Demo</h2>
<button @click="runWorker">Run in Worker</button>
<button @click="runMain">Run in Main Thread</button>
<div v-if="result">
<h3>Result: {{ result.result }}</h3>
<p>Duration: {{ result.duration }}ms</p>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const result = ref(null)
const runWorker = () => {
const worker = new Worker(new URL('../workers/demo.worker.js', import.meta.url))
worker.onmessage = (e) => {
result.value = e.data
worker.terminate()
}
worker.postMessage({ iterations: 1000000 })
}
const runMain = () => {
const start = performance.now()
let res = 0
for (let i = 0; i < 1000000; i++) {
res += Math.sqrt(i)
}
const duration = performance.now() - start
result.value = {
result: res.toFixed(2),
duration: duration.toFixed(2),
input: { iterations: 1000000 }
}
}
</script>
EOF
# 更新 App.vue
cat > src/App.vue << 'EOF'
<template>
<WorkerDemo />
</template>
<script setup>
import WorkerDemo from './components/WorkerDemo.vue'
</script>
EOF
# 安装依赖并启动
npm install
npm run dev
echo "✅ 项目创建完成!"
echo "🌐 访问: http://localhost:5173"
运行:
bash
bash
chmod +x setup-vue-worker.sh
./setup-vue-worker.sh
10. 关键注意事项
-
路径问题 :Vue 项目中需要使用
new URL('./worker.js', import.meta.url) -
模块类型 :如果需要 ES 模块,添加
{ type: 'module' } -
开发模式:Vite 在开发模式下会自动处理 Worker
-
生产构建:构建时会单独打包 Worker 文件
-
HMR 支持:Worker 修改后可能需要手动刷新
-
类型支持:如果使用 TypeScript,需要添加类型声明
11. TypeScript 支持
typescript
TypeScript
// src/workers/types.ts
export interface WorkerMessage {
type: string
data?: any
id?: string
}
export interface WorkerResult {
id?: string
type: string
result?: any
error?: string
success: boolean
}
// src/workers/typed.worker.ts
import type { WorkerMessage, WorkerResult } from './types'
const ctx: Worker = self as any
ctx.onmessage = function(e: MessageEvent<WorkerMessage>) {
// TypeScript 类型安全
const result: WorkerResult = {
id: e.data.id,
type: e.data.type,
result: 'processed',
success: true
}
ctx.postMessage(result)
}
这样你就可以在 Vue 3 中全面测试工作者线程了!