前端面试题之Vue3篇

748 阅读19分钟

Vue3 最热门面试题

核心概念

1. Vue3 相比 Vue2 有哪些重大改进?

答案

  • 性能提升:重写虚拟DOM,渲染性能提升1.3-2倍
  • 代码体积优化:通过tree-shaking减少约41%打包体积
  • Composition API:解决Options API在复杂组件中的逻辑组织问题
  • TypeScript支持:完全用TS重写,提供更好的类型推导
  • Teleport组件:可将内容传送到DOM的任何位置
  • Fragments:支持多根节点组件
  • Suspense组件:处理异步依赖
  • 响应式系统升级:使用Proxy替代Object.defineProperty

2. 什么是Composition API,与Options API相比有哪些优势?

答案

  • Composition API是Vue3引入的新API,允许使用setup函数组织组件逻辑
  • 优势
    • 更好的代码组织:按功能/逻辑关注点组织代码
    • 更好的逻辑复用:使用组合式函数(Composables)
    • 更好的类型推导:针对TypeScript优化
    • 更小的打包体积:支持tree-shaking

最佳实践

<script setup>
import { ref, onMounted, computed } from 'vue'

// 状态管理
const count = ref(0)
const doubleCount = computed(() => count.value * 2)

// 用户交互逻辑
function increment() {
  count.value++
}

// 生命周期
onMounted(() => {
  console.log('组件已挂载')
})
</script>

<template>
  <button @click="increment">{{ count }}</button>
  <p>双倍计数: {{ doubleCount }}</p>
</template>

3. Vue3中的响应式原理是什么?

答案

  • Vue3使用ES6的Proxy代替Vue2的Object.defineProperty实现响应式
  • Proxy可以拦截整个对象,而不是单个属性,解决了数组索引变化和对象属性添加/删除的监听问题
  • 通过reactive/ref等API创建响应式对象
  • 当访问响应式对象属性时,会跟踪依赖(track);当属性变更时,会触发更新(trigger)

代码示例

const state = reactive({
  count: 0
})

// 对比Vue2中需要的Vue.set或this.$set
// Vue3可以直接添加新属性并保持响应性
state.newProperty = 'value' // 直接添加,仍然具有响应性

4. ref和reactive有什么区别?

答案

  • ref
    • 适用于基本数据类型和对象
    • 需要通过.value访问和修改值
    • 自动解包(在模板和响应式对象中)
  • reactive
    • 只适用于对象(包括数组、Map、Set等)
    • 直接访问和修改属性
    • 不能替换整个对象(会丢失响应性)

最佳实践

// 简单类型用ref
const count = ref(0)

// 复杂对象用reactive
const user = reactive({
  name: '张三',
  age: 25,
  addresses: []
})

// 解构reactive对象时保持响应性
const { name, age } = toRefs(user)

高级特性

5. Vue3中如何实现组件通信?

答案

  • 父子组件:props/emits
  • 兄弟组件:通过共同父组件或状态管理
  • 跨层级组件:provide/inject
  • 全局状态管理:Pinia(推荐)或Vuex
  • 事件总线:使用第三方库或自定义事件发布订阅模式

最佳实践

<!-- 父组件 -->
<script setup>
import { provide, ref } from 'vue'
import ChildComponent from './ChildComponent.vue'

const message = ref('Hello from parent')
// 通过provide向下传递(包括修改方法)
provide('message', message)

function updateMessage(newValue) {
  message.value = newValue
}
provide('updateMessage', updateMessage)
</script>

<!-- 子组件 -->
<script setup>
import { inject } from 'vue'

const message = inject('message')
const updateMessage = inject('updateMessage')
</script>

6. 解释Vue3的生命周期钩子及其使用方法

答案

  • Vue3组合式API生命周期钩子:
    • onBeforeMount:组件挂载前
    • onMounted:组件挂载后
    • onBeforeUpdate:组件更新前
    • onUpdated:组件更新后
    • onBeforeUnmount:组件卸载前
    • onUnmounted:组件卸载后
    • onActivated:被keep-alive缓存的组件激活时
    • onDeactivated:被keep-alive缓存的组件停用时
    • onErrorCaptured:捕获后代组件错误

最佳实践

<script setup>
import { onMounted, onUpdated, onBeforeUnmount } from 'vue'

onMounted(() => {
  console.log('组件已挂载,可以进行DOM操作或API调用')
  // 数据获取、添加事件监听器等
})

onUpdated(() => {
  console.log('组件已更新')
  // 访问更新后的DOM
})

onBeforeUnmount(() => {
  console.log('组件即将卸载')
  // 清理事件监听器、定时器等
})
</script>

7. Vue3中如何处理表单?

答案

  • 使用v-model进行双向绑定
  • 支持多个v-model绑定(Vue3新特性)
  • 可以使用.lazy.number.trim修饰符
  • 自定义组件中使用definePropsdefineEmits实现v-model

最佳实践

<script setup>
import { ref } from 'vue'

// 基本用法
const username = ref('')
const password = ref('')

// 处理表单提交
function submitForm() {
  console.log({ username: username.value, password: password.value })
}
</script>

<template>
  <form @submit.prevent="submitForm">
    <input v-model.trim="username" placeholder="用户名" />
    <input v-model="password" type="password" placeholder="密码" />
    <button type="submit">提交</button>
  </form>
</template>

自定义组件v-model:

<script setup>
// 子组件
defineProps({
  modelValue: String
})
defineEmits(['update:modelValue'])
</script>

<template>
  <input 
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)" 
  />
</template>

<!-- 父组件中使用 -->
<CustomInput v-model="username" />

8. 什么是异步组件,Vue3中如何使用?

答案

  • 异步组件是一种可以延迟加载的组件,用于提高应用性能
  • Vue3通过defineAsyncComponent定义异步组件
  • 可以与Suspense组件配合使用,处理加载状态

最佳实践

<script setup>
import { defineAsyncComponent } from 'vue'

// 基本用法
const AsyncComp = defineAsyncComponent(() => 
  import('./components/HeavyComponent.vue')
)

// 高级用法(带加载和错误处理)
const AsyncCompWithOptions = defineAsyncComponent({
  loader: () => import('./components/HeavyComponent.vue'),
  loadingComponent: LoadingComponent,
  errorComponent: ErrorComponent,
  delay: 200,
  timeout: 5000
})
</script>

<template>
  <Suspense>
    <template #default>
      <AsyncComp />
    </template>
    <template #fallback>
      <div>加载中...</div>
    </template>
  </Suspense>
</template>

9. Vue3中如何使用TypeScript?

答案

  • Vue3完全用TS重写,提供一流的TS支持
  • 在组合式API中,类型推导更加自然
  • 使用<script lang="ts" setup>组合使用TS和组合式API
  • 使用definePropsdefineEmits时可以利用泛型提供类型

最佳实践

<script lang="ts" setup>
import { ref } from 'vue'

// 定义props类型
interface Props {
  title: string
  count?: number
}

const props = defineProps<Props>()

// 定义emits类型
const emit = defineEmits<{
  (e: 'update', value: string): void
  (e: 'delete', id: number): void
}>()

// 类型安全的refs
const username = ref<string>('')
const users = ref<Array<{ id: number, name: string }>>([])
</script>

10. 什么是Teleport组件,如何使用?

答案

  • Teleport是Vue3新增的内置组件,允许将内容渲染到DOM树的任何位置
  • 常用于模态框、提示框等需要突破组件层级限制的场景
  • 通过to属性指定目标容器

最佳实践

<template>
  <div>
    <button @click="showModal = true">显示模态框</button>
    
    <!-- 传送到body下,避免受父组件CSS影响 -->
    <Teleport to="body">
      <div v-if="showModal" class="modal">
        <div class="modal-content">
          <h3>模态框标题</h3>
          <p>模态框内容</p>
          <button @click="showModal = false">关闭</button>
        </div>
      </div>
    </Teleport>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const showModal = ref(false)
</script>

<style scoped>
.modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
}
.modal-content {
  background: white;
  padding: 20px;
  border-radius: 4px;
}
</style>

状态管理和路由

11. Vue3中推荐的状态管理方案是什么?

答案

  • Vue3官方推荐使用Pinia替代Vuex
  • Pinia优势:
    • 更简洁的API(无mutations)
    • 完整的TypeScript支持
    • 自动代码拆分
    • 更好的开发体验(支持Vue devtools)

最佳实践

// 定义store
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  // 状态
  state: () => ({
    name: '张三',
    isLoggedIn: false
  }),
  // 计算属性
  getters: {
    fullName: (state) => `${state.name}先生/女士`
  },
  // 操作方法
  actions: {
    async login(username, password) {
      const user = await api.login(username, password)
      this.name = user.name
      this.isLoggedIn = true
    },
    logout() {
      this.isLoggedIn = false
    }
  }
})

// 使用store
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()
console.log(userStore.name)
userStore.login('admin', '123456')

12. Vue Router在Vue3中有哪些变化?

答案

  • Vue Router 4对应Vue3,使用组合式API
  • 使用useRouteruseRoute替代this.routerthis.router和this.route
  • 提供onBeforeRouteUpdateonBeforeRouteLeave组合式API钩子
  • 改进的导航守卫和懒加载

最佳实践

<script setup>
import { onMounted } from 'vue'
import { useRouter, useRoute, onBeforeRouteLeave } from 'vue-router'

const router = useRouter()
const route = useRoute()

// 访问路由参数
const userId = route.params.id

// 编程式导航
function navigateToHome() {
  router.push('/')
}

// 导航守卫
onBeforeRouteLeave((to, from) => {
  const answer = window.confirm('确定要离开吗?')
  if (!answer) return false
})

onMounted(() => {
  console.log(`当前页面路径:${route.path}`)
})
</script>

性能优化

13. Vue3中的性能优化技术有哪些?

答案

  • 静态提升:静态节点只创建一次并重复使用
  • 补丁标记:为动态节点添加类型标记,减少比较
  • 树结构打平:减少虚拟DOM嵌套
  • 组件Fast Path:优化静态组件速度
  • 按需编译:使用v-oncev-memo减少不必要的重渲染
  • 异步组件:按需加载组件
  • 更好的tree-shaking:减少应用体积
  • 片段(Fragments):减少无谓的DOM节点

最佳实践

<script setup>
import { ref, computed } from 'vue'

const list = ref([1, 2, 3])

// 使用v-memo减少重渲染
const expensive = computed(() => {
  console.log('复杂计算')
  return list.value.map(x => x * x)
})
</script>

<template>
  <!-- v-once处理静态内容 -->
  <header v-once>
    <h1>标题不会变</h1>
  </header>
  
  <!-- v-memo优化动态列表渲染 -->
  <div v-for="(item, index) in list" :key="index" v-memo="[item]">
    {{ expensiveOperation(item) }}
  </div>
</template>

14. 如何处理Vue3中的大型列表渲染?

答案

  • 使用虚拟滚动:只渲染可见区域的项
  • 使用v-memo优化项的重渲染
  • 展平响应式数据结构
  • 使用不可变数据结构
  • 合理使用key属性

最佳实践

<script setup>
import { ref } from 'vue'
import { VirtualList } from 'some-virtual-list-library'

const items = ref(Array.from({ length: 10000 }, (_, i) => ({
  id: i,
  text: `项目 ${i}`
})))

// 使用节流函数处理滚动事件
function handleScroll(e) {
  // 实现节流逻辑
}
</script>

<template>
  <VirtualList
    :items="items"
    :item-height="50"
    v-slot="{ item, index }"
  >
    <!-- 使用v-memo避免不必要的重渲染 -->
    <div
      class="list-item"
      v-memo="[item.id, item.text]"
    >
      {{ index }}. {{ item.text }}
    </div>
  </VirtualList>
</template>

高级面试题

15. 解释Vue3中的依赖注入(provide/inject)

答案

  • provide/inject用于跨层级组件通信
  • 提供响应式数据时,需要确保引用不变(传递ref或reactive对象)
  • 可以提供修改数据的方法,保持单向数据流
  • 有类型支持(TypeScript)

最佳实践

<!-- 父组件 -->
<script setup>
import { provide, readonly, ref } from 'vue'

const count = ref(0)
// 提供只读版本(保持单向数据流)
provide('count', readonly(count))
// 提供修改方法
provide('increment', () => count.value++)
</script>

<!-- 后代组件 -->
<script setup>
import { inject } from 'vue'

// 带默认值的注入
const count = inject('count', ref(0))
const increment = inject('increment', () => {})
</script>

16. 如何在Vue3中实现自定义指令?

答案

  • Vue3中自定义指令API变化,钩子函数与组件生命周期一致
  • 可以通过app.directive全局注册或组件内局部注册
  • 支持修饰符和参数

最佳实践

// 注册全局自定义指令
app.directive('focus', {
  mounted(el) {
    el.focus()
  }
})

// 带参数的复杂指令
app.directive('pin', {
  mounted(el, binding) {
    el.style.position = 'fixed'
    // 获取指令值、参数和修饰符
    const value = binding.value || 200
    const position = binding.arg || 'top'
    const withTransition = binding.modifiers.transition
    
    el.style[position] = value + 'px'
    if (withTransition) {
      el.style.transition = 'all 0.5s'
    }
  },
  updated(el, binding) {
    // 更新时重新计算
  }
})

// 使用
<div v-pin:right.transition="50">固定在右侧50px位置</div>

17. 解释Vue3中的setup函数和<script setup>的区别?

答案

  • setup函数
    • Composition API的入口点
    • 需要手动返回暴露的数据和方法
    • 可以访问props、context等参数
  • <script setup>
    • 语法糖,简化模板中使用的变量和函数导出
    • 顶层变量和函数自动暴露给模板
    • 使用definePropsdefineEmits声明props和事件
    • 支持defineExpose暴露内部属性给父组件

最佳实践

<!-- setup函数方式 -->
<script>
import { ref } from 'vue'

export default {
  props: {
    initialCount: Number
  },
  setup(props) {
    const count = ref(props.initialCount || 0)
    
    function increment() {
      count.value++
    }
    
    // 必须显式返回
    return {
      count,
      increment
    }
  }
}
</script>

<!-- script setup方式 -->
<script setup>
import { ref } from 'vue'

// 自动推断类型
const props = defineProps({
  initialCount: Number
})

const count = ref(props.initialCount || 0)

function increment() {
  count.value++
}

// 不需要返回,自动暴露
</script>

18. Vue3中如何处理错误?

答案

  • 组件级错误使用onErrorCaptured生命周期钩子
  • 应用级错误使用app.config.errorHandler
  • 异步错误处理使用try/catchpromise.catch()
  • 使用自定义错误边界组件

最佳实践

<!-- 错误边界组件 -->
<script setup>
import { ref, onErrorCaptured } from 'vue'

const error = ref(null)
const errorInfo = ref(null)

onErrorCaptured((err, instance, info) => {
  error.value = err
  errorInfo.value = info
  // 返回false阻止错误继续传播
  return false
})
</script>

<template>
  <div>
    <slot v-if="!error"></slot>
    <div v-else class="error-boundary">
      <h2>出错了</h2>
      <p>{{ error.message }}</p>
      <button @click="error = null">重试</button>
    </div>
  </div>
</template>

<!-- 应用级错误处理 -->
<script>
// main.js
app.config.errorHandler = (err, instance, info) => {
  // 发送到错误跟踪服务
  reportError(err, {component: instance, info})
  console.error('应用错误:', err)
}
</script>

19. Vue3中的渲染函数和JSX

答案

  • Vue3的渲染函数API有重大变化,使用h函数创建vnode
  • 支持Fragment、Teleport等新特性
  • 更好的TypeScript支持
  • 支持JSX/TSX语法(需配置Babel或TypeScript)

最佳实践

// 渲染函数
import { h, ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    
    return () => h('div', {
      class: 'container',
      onClick: () => count.value++
    }, [
      h('h1', `点击次数: ${count.value}`),
      h('button', { onClick: () => count.value++ }, '增加')
    ])
  }
}

// JSX语法
export default {
  setup() {
    const count = ref(0)
    
    return () => (
      <div class="container" onClick={() => count.value++}>
        <h1>点击次数: {count.value}</h1>
        <button onClick={() => count.value++}>增加</button>
      </div>
    )
  }
}

20. Vue3中如何实现函数式组件?

答案

  • Vue3中移除了functional属性,但仍支持函数式组件
  • 使用普通函数返回VNode
  • 通过JSX或h函数实现
  • 适用于无状态、纯展示的组件

最佳实践

// 函数式组件
import { h } from 'vue'

// 普通函数即为函数式组件
const FunctionalComp = (props, { slots, attrs, emit }) => {
  return h('div', { ...attrs }, [
    h('h1', null, props.title),
    slots.default?.()
  ])
}

// 使用JSX
const FunctionalJsx = (props, { slots }) => (
  <div>
    <h1>{props.title}</h1>
    {slots.default?.()}
  </div>
)

// 使用
<FunctionalComp title="标题">内容</FunctionalComp>

21. Vue3中的Suspense组件是什么?如何使用?

答案

  • Suspense是Vue3新增的内置组件,用于处理异步依赖
  • 可以在异步组件或setup函数返回Promise时显示加载状态
  • 提供两个插槽:default和fallback
  • 支持嵌套使用和错误处理

最佳实践

<template>
  <Suspense>
    <!-- 异步内容 -->
    <template #default>
      <UserProfile />
    </template>
    <!-- 加载状态 -->
    <template #fallback>
      <div class="loading">
        <span class="loader"></span>
        数据加载中...
      </div>
    </template>
  </Suspense>
</template>

<script setup>
import { ref, onErrorCaptured } from 'vue'
import UserProfile from './UserProfile.vue'

// 处理Suspense错误
const error = ref(null)
onErrorCaptured((e) => {
  error.value = e
  return false // 阻止错误继续传播
})
</script>

22. Vue3的SFC单文件组件有哪些新特性?

答案

  • <script setup>语法糖
  • CSS变量注入(v-bind在样式中使用)
  • 多根节点组件支持
  • <style scoped>改进
  • 状态驱动的CSS变量
  • 自定义块支持增强

最佳实践

<script setup>
import { ref } from 'vue'

const color = ref('red')
const fontSize = ref(16)
</script>

<template>
  <div class="card">动态样式示例</div>
  <button @click="color = color === 'red' ? 'blue' : 'red'">
    切换颜色
  </button>
</template>

<style scoped>
/* CSS变量绑定 */
.card {
  color: v-bind(color);
  font-size: v-bind(fontSize + 'px');
  transition: all 0.3s;
}
</style>

23. Vue3 的自定义Hooks(组合式函数)如何实现和使用?

答案

  • 组合式函数(Composables)是Vue3中逻辑复用的推荐方式
  • 命名约定以"use"开头
  • 返回响应式状态和方法
  • 可组合、可重用、可测试

最佳实践

// useCounter.js
import { ref, computed } from 'vue'

export function useCounter(initialValue = 0, options = {}) {
  const count = ref(initialValue)
  const { min, max } = options
  
  const increment = () => {
    if (max !== undefined && count.value >= max) return
    count.value++
  }
  
  const decrement = () => {
    if (min !== undefined && count.value <= min) return
    count.value--
  }
  
  const reset = () => count.value = initialValue
  
  const isMax = computed(() => max !== undefined && count.value >= max)
  const isMin = computed(() => min !== undefined && count.value <= min)
  
  return {
    count,
    increment,
    decrement,
    reset,
    isMax,
    isMin
  }
}

// 使用
<script setup>
import { useCounter } from './composables/useCounter'

const { count, increment, decrement, isMin, isMax } = useCounter(0, { min: 0, max: 10 })
</script>

<template>
  <div class="counter">
    <button @click="decrement" :disabled="isMin">-</button>
    <span>{{ count }}</span>
    <button @click="increment" :disabled="isMax">+</button>
  </div>
</template>

24. Vue3中watch和watchEffect的区别是什么?

答案

  • watch
    • 需明确指定要监听的响应式引用
    • 可同时监听多个数据源
    • 能够访问变化前后的值
    • 支持惰性执行(默认不立即执行)
  • watchEffect
    • 自动追踪依赖并在依赖变化时触发回调
    • 立即执行一次以收集依赖
    • 无法获取变化前的值
    • 代码更简洁但追踪依赖不明确

最佳实践

<script setup>
import { ref, watch, watchEffect } from 'vue'

const firstName = ref('张')
const lastName = ref('三')
const fullName = ref('')

// watch - 明确监听,可获取新旧值
watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
  console.log(`名由${oldFirst}变为${newFirst}`)
  console.log(`姓由${oldLast}变为${newLast}`)
  fullName.value = newFirst + newLast
})

// watchEffect - 自动追踪依赖
watchEffect(() => {
  // 自动追踪firstName和lastName依赖
  fullName.value = firstName.value + lastName.value
  console.log(`姓名更新为: ${fullName.value}`)
})

// 使用场景:
// - watch:当需要比较变化前后的值,或控制执行时机
// - watchEffect:当只关心最新状态,或依赖较多时
</script>

25. Vue3与TypeScript结合使用的最佳实践是什么?

答案

  • 使用<script lang="ts" setup>简化TS开发
  • 使用defineProps<Props>()而非defineProps({})获取更好类型推导
  • 利用typeofReturnType获取复杂类型
  • 为组合式函数添加泛型
  • 使用.d.ts文件扩展Vue类型

最佳实践

<script lang="ts" setup>
// 类型声明
interface User {
  id: number
  name: string
  email: string
  age?: number
}

// TypeScript版props定义
interface Props {
  user: User
  isActive: boolean
}

const props = defineProps<Props>()

// 带默认值的props
withDefaults(defineProps<Props>(), {
  isActive: false
})

// 类型安全的emits
const emit = defineEmits<{
  (e: 'update', user: User): void
  (e: 'delete', id: number): void
}>()

// 泛型组合式函数
function useAsync<T>(asyncFn: () => Promise<T>) {
  const data = ref<T | null>(null)
  const error = ref<Error | null>(null)
  const loading = ref(false)
  
  const execute = async () => {
    loading.value = true
    data.value = null
    error.value = null
    
    try {
      data.value = await asyncFn()
    } catch (err) {
      error.value = err as Error
    } finally {
      loading.value = false
    }
  }
  
  return { data, error, loading, execute }
}

// 使用
const { data: users, loading, execute } = useAsync<User[]>(fetchUsers)
</script>

26. Vue3中的性能监控和优化方法有哪些?

答案

  • 使用浏览器性能工具分析组件渲染
  • 使用Vue Devtools的性能面板
  • 使用defineAsyncComponent懒加载组件
  • 适当使用v-oncev-memo减少重渲染
  • 适当使用shallowRefshallowReactive减少响应式开销
  • 避免不必要的组件嵌套
  • 合理使用computed缓存计算结果
  • 使用keep-alive缓存组件实例

最佳实践

<script setup>
import { shallowRef, markRaw, nextTick } from 'vue'

// 使用shallowRef处理大数据集合
const users = shallowRef([])

// 非响应式对象,避免不必要的代理
const staticOptions = markRaw({
  title: '标题',
  width: 600
})

// 避免在同一个事件循环中多次修改响应式对象
async function batchUpdate() {
  users.value[0].name = '新名字'
  users.value[0].email = '新邮箱'
  
  // 使用nextTick等待DOM更新完成
  await nextTick()
  // DOM已更新,可以进行DOM操作
}
</script>

<template>
  <!-- 使用v-memo减少不必要的VNode创建 -->
  <div v-for="item in longList" :key="item.id" v-memo="[item.id, item.isActive]">
    <!-- 复杂内容 -->
  </div>

  <!-- 缓存组件实例 -->
  <keep-alive :include="['UserList', 'UserDetail']">
    <component :is="currentComponent" />
  </keep-alive>
</template>

27. 如何在Vue3中管理大型项目的状态和架构?

答案

  • 使用Pinia进行状态管理,按功能模块拆分store
  • 采用基于组合式API的分层架构(数据、逻辑、视图分离)
  • 使用组合式函数(Composables)封装可重用逻辑
  • 实现特性标志(Feature Flags)系统
  • 设计微前端架构(适用超大型应用)
  • 配置模块联邦实现代码共享

最佳实践

// 分层架构示例

// 数据层 - API接口
// api/userApi.js
export const userApi = {
  getUsers() { /* 实现 */ },
  createUser(user) { /* 实现 */ },
  updateUser(id, user) { /* 实现 */ }
}

// 状态层 - Pinia store
// stores/user.js
import { defineStore } from 'pinia'
import { userApi } from '@/api/userApi'

export const useUserStore = defineStore('user', {
  state: () => ({
    users: [],
    loading: false
  }),
  actions: {
    async fetchUsers() {
      this.loading = true
      try {
        this.users = await userApi.getUsers()
      } finally {
        this.loading = false
      }
    }
  }
})

// 逻辑层 - 组合式函数
// composables/useUserManagement.js
import { ref, computed } from 'vue'
import { useUserStore } from '@/stores/user'

export function useUserManagement() {
  const userStore = useUserStore()
  const searchQuery = ref('')
  
  const filteredUsers = computed(() => {
    return userStore.users.filter(user => 
      user.name.toLowerCase().includes(searchQuery.value.toLowerCase())
    )
  })
  
  // 其他业务逻辑...
  
  return {
    users: filteredUsers,
    searchQuery,
    loading: computed(() => userStore.loading),
    fetchUsers: userStore.fetchUsers
  }
}

// 视图层 - 组件
// UserList.vue
<script setup>
import { useUserManagement } from '@/composables/useUserManagement'

const { users, searchQuery, loading, fetchUsers } = useUserManagement()

// 仅处理视图逻辑
</script>

28. Vue3中的自定义渲染器是什么?如何应用?

答案

  • 自定义渲染器API允许将Vue的响应式系统用于DOM以外的平台
  • 通过createRenderer创建自定义渲染器
  • 实现nodeOps(节点操作)和patchProp(属性更新)
  • 应用场景:Canvas、WebGL、终端UI、移动原生渲染等

最佳实践

// 简易Canvas渲染器示例
import { createRenderer } from 'vue'

// 定义Canvas元素
class CanvasElement {
  constructor(type) {
    this.type = type
    this.props = {}
    this.children = []
    // 其他属性...
  }
}

// 创建自定义渲染器
const CanvasRenderer = createRenderer({
  // 创建元素
  createElement(type) {
    return new CanvasElement(type)
  },
  
  // 设置元素文本
  setElementText(node, text) {
    node.text = text
  },
  
  // 插入元素
  insert(child, parent, anchor) {
    if (!parent.children.includes(child)) {
      if (anchor) {
        const i = parent.children.indexOf(anchor)
        parent.children.splice(i, 0, child)
      } else {
        parent.children.push(child)
      }
    }
    // 触发重绘
    renderCanvas(rootElement, canvasContext)
  },
  
  // 更新属性
  patchProp(el, key, prevValue, nextValue) {
    el.props[key] = nextValue
    // 触发重绘
    renderCanvas(rootElement, canvasContext)
  },
  
  // 移除元素
  remove(el) {
    const parent = el.parent
    if (parent) {
      const i = parent.children.indexOf(el)
      if (i > -1) parent.children.splice(i, 1)
      // 触发重绘
      renderCanvas(rootElement, canvasContext)
    }
  }
  
  // 其他必要的实现...
})

// 渲染函数
function renderCanvas(rootElement, ctx) {
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
  // 递归渲染元素
  // 实现绘制逻辑...
}

// 使用
const app = CanvasRenderer.createApp({
  setup() {
    // 组件逻辑
    return () => {
      // 返回虚拟DOM
    }
  }
})

// 挂载到Canvas容器
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
const rootElement = new CanvasElement('root')
app.mount(rootElement)

29. Vue3中如何处理服务端渲染(SSR)?

答案

  • Vue3的SSR通过@vue/server-renderer包实现
  • 将组件渲染为HTML字符串发送给客户端
  • 客户端进行"水合"(hydration),添加交互能力
  • 可与Nuxt.js等框架结合使用
  • 支持异步数据预取和流式渲染

最佳实践

// server.js - 基础SSR实现
import { createSSRApp } from 'vue'
import { renderToString } from '@vue/server-renderer'
import express from 'express'
import App from './App.vue'
import { createRouter } from './router'
import { createPinia } from 'pinia'

const server = express()

server.get('*', async (req, res) => {
  // 为每个请求创建新应用实例
  const app = createSSRApp(App)
  const pinia = createPinia()
  const router = createRouter()
  
  app.use(pinia)
  app.use(router)
  
  // 设置路由位置
  await router.push(req.url)
  await router.isReady()
  
  // 获取匹配的组件
  const matchedComponents = router.currentRoute.value.matched
  
  // 预取数据
  try {
    await Promise.all(matchedComponents.map(component => {
      if (component.serverPrefetch) {
        return component.serverPrefetch()
      }
    }))
  } catch (error) {
    console.error('数据预取错误:', error)
    res.status(500).send('服务器错误')
    return
  }
  
  // 渲染HTML
  const appHtml = await renderToString(app)
  const state = JSON.stringify(pinia.state.value)
  
  const html = `
    <!DOCTYPE html>
    <html>
      <head>
        <title>Vue3 SSR</title>
      </head>
      <body>
        <div id="app">${appHtml}</div>
        <script>window.__INITIAL_STATE__=${state}</script>
        <script src="/main.js"></script>
      </body>
    </html>
  `
  
  res.setHeader('Content-Type', 'text/html')
  res.send(html)
})

server.listen(3000)

30. Vue3中的WebComponents集成方式是什么?

答案

  • Vue3支持将组件定义为自定义元素(Web Components)
  • 通过defineCustomElement API实现
  • 可在任何框架或无框架环境中使用
  • 支持属性和事件通信
  • 样式被封装在Shadow DOM中

最佳实践

// 定义Vue组件作为Web Components
import { defineCustomElement } from 'vue'

// 组件定义
const MyCounter = defineCustomElement({
  props: {
    initial: Number
  },
  emits: ['change'],
  setup(props, { emit }) {
    const count = ref(props.initial || 0)
    
    const increment = () => {
      count.value++
      emit('change', count.value)
    }
    
    return { count, increment }
  },
  template: `
    <div class="counter">
      <span>{{ count }}</span>
      <button @click="increment">+1</button>
    </div>
  `,
  styles: [`
    .counter {
      display: inline-block;
      padding: 10px;
      border: 1px solid #ccc;
      border-radius: 4px;
    }
    button {
      margin-left: 10px;
    }
  `]
})

// 注册自定义元素
customElements.define('my-counter', MyCounter)

// 在HTML中使用
// <my-counter initial="10" @change="handleChange"></my-counter>

// 在其他框架中使用
// React示例
function ReactComponent() {
  const counterRef = useRef(null)
  
  useEffect(() => {
    const counter = counterRef.current
    const handleChange = (e) => console.log('计数变化:', e.detail)
    counter.addEventListener('change', handleChange)
    return () => counter.removeEventListener('change', handleChange)
  }, [])
  
  return <my-counter ref={counterRef} initial={10} />
}

31. 递归组件与动态组件的高级应用

答案

  • 递归组件通过自引用实现嵌套数据的渲染,如树形结构
  • 动态组件使用:is特性动态切换组件
  • 二者结合可以实现高度灵活的组件系统

最佳实践

<!-- TreeNode.vue - 递归组件示例 -->
<script setup>
import { defineProps } from 'vue'

defineProps({
  node: {
    type: Object,
    required: true
  }
})
</script>

<template>
  <div class="tree-node">
    <div class="node-content">{{ node.label }}</div>
    <div v-if="node.children && node.children.length" class="node-children">
      <!-- 自引用组件 -->
      <TreeNode 
        v-for="child in node.children" 
        :key="child.id" 
        :node="child" 
      />
    </div>
  </div>
</template>

<!-- 动态递归组件系统 -->
<script setup>
import { ref, markRaw, shallowRef } from 'vue'

// 组件注册表
const componentRegistry = {
  text: markRaw(TextComponent),
  image: markRaw(ImageComponent),
  container: markRaw(ContainerComponent),
  // 更多组件...
}

// 渲染树
const renderTree = ref({
  type: 'container',
  props: { direction: 'vertical' },
  children: [
    { type: 'text', props: { content: '标题' } },
    {
      type: 'container',
      props: { direction: 'horizontal' },
      children: [
        { type: 'image', props: { src: 'img1.jpg' } },
        { type: 'text', props: { content: '描述文字' } }
      ]
    }
  ]
})

// 使用shallowRef提高性能
const currentComponent = shallowRef(null)
</script>

<template>
  <component 
    :is="componentRegistry[node.type] || 'div'"
    v-for="(node, index) in renderTree"
    :key="index"
    v-bind="node.props"
  >
    <!-- 递归渲染子节点 -->
    <template v-if="node.children">
      <component 
        v-for="(child, childIndex) in node.children"
        :key="childIndex"
        :is="componentRegistry[child.type] || 'div'"
        v-bind="child.props"
      />
    </template>
  </component>
</template>

32. Vue宏和编译时优化

答案

  • Vue3引入了宏(Macros)概念,如definePropsdefineEmits
  • 宏是在编译时处理的函数,不会出现在运行时代码中
  • 编译时优化减少运行时开销,提高性能
  • 可使用defineCustomElementwithDefaults等宏

最佳实践

<script setup>
// Vue宏示例

// 1. defineProps:编译时处理的Props声明
const props = defineProps({
  title: String,
  items: Array
})

// 2. withDefaults:为Props提供默认值的宏
const props2 = withDefaults(defineProps<{
  message: string
  count?: number
}>(), {
  count: 0
})

// 3. defineEmits:编译时处理的事件声明
const emit = defineEmits(['change', 'update'])

// 4. defineExpose:显式暴露内部属性
const name = ref('组件内部值')
const internalMethod = () => {}
defineExpose({
  name,
  callMethod: internalMethod
})

// 5. defineOptions:设置组件选项
defineOptions({
  name: 'MyComponent',
  inheritAttrs: false
})

// 6. defineSlots:类型安全的插槽定义
defineSlots<{
  default(props: { item: string }): any
  header(props: { title: string }): any
}>()
</script>

33. Vue3中的Transition和TransitionGroup高级应用

答案

  • Transition处理单元素/组件的进入/离开过渡
  • TransitionGroup处理列表过渡动画,维持DOM结构
  • 支持CSS过渡、CSS动画和JavaScript钩子
  • 可结合GSAP等动画库实现复杂动画

最佳实践

<script setup>
import { ref } from 'vue'
import gsap from 'gsap'

const items = ref([1, 2, 3, 4, 5])
const show = ref(true)

function addItem() {
  items.value.push(items.value.length + 1)
}

function removeItem(index) {
  items.value.splice(index, 1)
}

function shuffle() {
  items.value = items.value.sort(() => Math.random() - 0.5)
}

// JavaScript钩子实现高级动画
const onBeforeEnter = (el) => {
  el.style.opacity = 0
  el.style.transform = 'scale(0.5)'
}

const onEnter = (el, done) => {
  gsap.to(el, {
    opacity: 1,
    scale: 1,
    duration: 0.5,
    onComplete: done
  })
}

const onLeave = (el, done) => {
  gsap.to(el, {
    opacity: 0,
    scale: 0.5,
    x: 100,
    duration: 0.5,
    onComplete: done
  })
}
</script>

<template>
  <button @click="show = !show">切换</button>
  
  <!-- 基本过渡 -->
  <Transition name="fade">
    <p v-if="show">Hello Vue3</p>
  </Transition>
  
  <!-- JavaScript钩子实现的高级动画 -->
  <Transition
    @before-enter="onBeforeEnter"
    @enter="onEnter"
    @leave="onLeave"
    :css="false"
  >
    <div v-if="show" class="gsap-box"></div>
  </Transition>
  
  <!-- 列表过渡 -->
  <div class="controls">
    <button @click="addItem">添加</button>
    <button @click="shuffle">打乱顺序</button>
  </div>
  
  <TransitionGroup
    name="list"
    tag="ul"
    class="list"
  >
    <li v-for="(item, index) in items" :key="item" @click="removeItem(index)">
      {{ item }}
    </li>
  </TransitionGroup>
</template>

<style>
/* 淡入淡出过渡 */
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

/* 列表过渡 */
.list-enter-active,
.list-leave-active {
  transition: all 0.5s ease;
}
.list-enter-from {
  opacity: 0;
  transform: translateX(30px);
}
.list-leave-to {
  opacity: 0;
  transform: translateX(-30px);
}
/* 移动过渡 */
.list-move {
  transition: transform 0.5s ease;
}
</style>

34. Vue3中的响应式系统深入原理

答案

  • 使用ES6 Proxy替代Object.defineProperty
  • 通过get操作跟踪依赖(track)
  • 通过set操作触发更新(trigger)
  • 使用WeakMap和Set存储依赖关系
  • 实现延迟计算和批量更新
  • 使用不同级别的响应式API:reactive、ref、readonly、shallowReactive等

代码实现核心原理

// Vue3响应式系统核心原理简化实现

// 当前激活的副作用函数
let activeEffect = null

// 存储响应式对象的依赖关系
// WeakMap<target, Map<key, Set<effect>>>
const targetMap = new WeakMap()

// 追踪依赖
function track(target, key) {
  if (!activeEffect) return
  
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }
  
  // 添加依赖
  dep.add(activeEffect)
}

// 触发更新
function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  
  const dep = depsMap.get(key)
  if (!dep) return
  
  // 执行所有副作用函数
  dep.forEach(effect => {
    if (effect.scheduler) {
      // 如果有调度器,使用调度器执行
      effect.scheduler()
    } else {
      effect()
    }
  })
}

// 创建响应式对象
function reactive(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      const result = Reflect.get(target, key, receiver)
      // 依赖追踪
      track(target, key)
      return result
    },
    set(target, key, value, receiver) {
      const oldValue = target[key]
      const result = Reflect.set(target, key, value, receiver)
      // 值变化时触发更新
      if (oldValue !== value) {
        trigger(target, key)
      }
      return result
    }
  })
}

// 创建副作用函数
function effect(fn, options = {}) {
  const effectFn = () => {
    try {
      activeEffect = effectFn
      // 执行原始函数
      return fn()
    } finally {
      activeEffect = null
    }
  }
  
  // 存储选项
  effectFn.scheduler = options.scheduler
  
  // 立即执行一次,收集依赖
  if (!options.lazy) {
    effectFn()
  }
  
  return effectFn
}

// 使用示例
const state = reactive({ count: 0 })

// 创建副作用
effect(() => {
  console.log('Count changed:', state.count)
})

// 修改会触发副作用执行
state.count++

35. Vue3自定义指令系统深度解析

答案

  • Vue3指令生命周期与组件生命周期保持一致
  • 指令可以接收复杂的动态参数和修饰符
  • 可通过函数简写指令(等同于mounted和updated钩子)
  • 使用场景:DOM直接操作、第三方库集成、跨组件交互

最佳实践

// 全功能自定义指令
app.directive('highlight', {
  // 在绑定元素的父组件挂载之前调用
  beforeMount(el, binding) {
    // binding对象包含:
    // value: 指令绑定的值
    // oldValue: 之前的值(仅在beforeUpdate和updated中可用)
    // arg: 传入指令的参数,如v-highlight:arg
    // modifiers: 修饰符对象,如v-highlight.foo.bar中的{foo: true, bar: true}
    // instance: 使用该指令的组件实例
    // dir: 指令定义对象
    
    const { value, arg, modifiers } = binding
    
    // 解析颜色参数
    const color = arg || 'yellow'
    
    // 应用基础高亮
    el.style.backgroundColor = color
    
    // 处理修饰符
    if (modifiers.bold) {
      el.style.fontWeight = 'bold'
    }
    
    if (modifiers.italic) {
      el.style.fontStyle = 'italic'
    }
    
    // 处理复杂值
    if (typeof value === 'object') {
      if (value.textColor) {
        el.style.color = value.textColor
      }
      if (value.fontSize) {
        el.style.fontSize = value.fontSize + 'px'
      }
    }
  },
  
  // 在包含组件的VNode更新后调用,但可能在子组件更新之前
  updated(el, binding) {
    // 处理值变化...
  },
  
  // 在绑定元素的父组件卸载后调用
  unmounted(el) {
    // 清理...
  }
})

// 功能型指令 - 点击外部
app.directive('click-outside', {
  mounted(el, binding) {
    el._clickOutsideHandler = (event) => {
      if (el !== event.target && !el.contains(event.target)) {
        binding.value(event)
      }
    }
    document.addEventListener('click', el._clickOutsideHandler)
  },
  unmounted(el) {
    document.removeEventListener('click', el._clickOutsideHandler)
    delete el._clickOutsideHandler
  }
})

// 简写自定义指令 (相当于mounted和updated)
app.directive('focus', (el, binding) => {
  // 聚焦元素
  if (binding.value) {
    el.focus()
  }
})

// 使用
<div v-highlight:red.bold.italic="{ textColor: 'white', fontSize: 16 }">
  高亮文字
</div>

<div v-click-outside="onClickOutside" class="dropdown">
  下拉菜单内容
</div>

36. Vue3中处理内存泄漏的方法

答案

  • 在组件卸载时清理定时器
  • 移除手动添加的事件监听器
  • 解除对大型对象的引用
  • 正确处理响应式副作用
  • 优化闭包使用方式

最佳实践

<script setup>
import { ref, onMounted, onBeforeUnmount, watchEffect } from 'vue'

const data = ref(null)

// 定时器示例
let timer = null
onMounted(() => {
  // 设置定时器
  timer = setInterval(() => {
    console.log('轮询...')
  }, 1000)
  
  // 添加事件监听器
  window.addEventListener('resize', handleResize)
  
  // 第三方库实例
  const chart = new SomeChartLibrary('#chart')
  
  // 在组件卸载时清理
  onBeforeUnmount(() => {
    // 清理定时器
    clearInterval(timer)
    
    // 移除事件监听器
    window.removeEventListener('resize', handleResize)
    
    // 销毁第三方库实例
    chart.destroy()
    
    // 解除对大型数据的引用
    data.value = null
  })
})

// 使用watchEffect时正确停止侦听
const stop = watchEffect(() => {
  // 一些副作用...
})

// 组件卸载时停止侦听
onBeforeUnmount(() => {
  stop() // 停止watchEffect
})

// 使用组合函数抽象清理逻辑
function useEventListener(target, event, callback) {
  onMounted(() => target.addEventListener(event, callback))
  onBeforeUnmount(() => target.removeEventListener(event, callback))
}

// 使用
const handleResize = () => { /* 处理窗口大小变化 */ }
useEventListener(window, 'resize', handleResize)
</script>

37. Vue3插件系统高级用法

答案

  • 插件可扩展全局属性、组件、指令等
  • 使用app.use()安装插件
  • 插件可以接收选项配置参数
  • 可以注入全局属性、mixin、自定义指令
  • 开发插件时需考虑TypeScript类型支持

最佳实践

// 定义插件
const myPlugin = {
  install(app, options = {}) {
    // 1. 添加全局属性
    app.config.globalProperties.$myAPI = {
      getData() {
        return options.baseURL ? 
          fetch(`${options.baseURL}/data`) : 
          Promise.resolve([])
      },
      formatDate(date) {
        return new Date(date).toLocaleDateString()
      }
    }
    
    // 2. 注册全局组件
    app.component('MyButton', {
      props: ['label'],
      template: `
        <button class="my-button" :class="options.theme">
          {{ label }}
        </button>
      `,
      setup() {
        return { options }
      }
    })
    
    // 3. 注册自定义指令
    app.directive('highlight', {
      mounted(el) {
        el.style.backgroundColor = options.highlightColor || 'yellow'
      }
    })
    
    // 4. 添加实例方法
    app.mixin({
      methods: {
        $notify(message) {
          // 实现消息通知功能
          console.log('通知:', message)
        }
      }
    })
    
    // 5. 注册组合式函数
    app.provide('useMyFeature', () => {
      // 实现功能逻辑
      return { /* API */ }
    })
    
    // 6. 配置Vue应用
    if (options.debug) {
      app.config.performance = true
      app.config.warnHandler = (msg) => {
        console.warn(`[自定义警告]: ${msg}`)
      }
    }
  }
}

// 使用插件
import { createApp } from 'vue'
import App from './App.vue'
import myPlugin from './plugins/myPlugin'

const app = createApp(App)

app.use(myPlugin, {
  baseURL: 'https://api.example.com',
  theme: 'dark',
  highlightColor: '#e2f7f0',
  debug: true
})

app.mount('#app')

// 在组件中使用
<script setup>
import { inject, getCurrentInstance } from 'vue'

// 方式1: 通过inject访问
const useMyFeature = inject('useMyFeature')
const { /* API */ } = useMyFeature()

// 方式2: 通过globalProperties访问
const instance = getCurrentInstance()
const data = await instance.proxy.$myAPI.getData()

// 使用实例方法
instance.proxy.$notify('操作成功')
</script>

<template>
  <!-- 使用全局注册的组件 -->
  <MyButton label="点击" />
  
  <!-- 使用自定义指令 -->
  <p v-highlight>高亮文本</p>
</template>

38. Vue3中的编译器API和自定义块处理

答案

  • Vue3暴露了编译器API,允许自定义编译过程
  • SFC支持自定义块,如<i18n><docs>
  • 可以通过Vite/Webpack插件处理自定义块
  • 编译器支持静态提升、树摇动、PatchFlags等优化

最佳实践

// 1. 使用编译器API
import { compile } from '@vue/compiler-dom'

// 编译模板
const result = compile(`
  <div>{{ message }}</div>
`, {
  // 配置项
  hoistStatic: true,
  cacheHandlers: true,
  prefixIdentifiers: true
})

console.log(result.code) // 生成的渲染函数代码

// 2. Vite插件处理自定义块
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [
    vue({
      // 处理<docs>自定义块
      customElement: /^docs$/
    }),
    {
      // 自定义块处理器
      name: 'vue-docs-block',
      transform(code, id) {
        // 处理.vue文件中的<docs>块
        if (!/\.vue$/.test(id)) return
        
        if (/<docs>/.test(code)) {
          const docsMatch = code.match(/<docs>([\s\S]*)<\/docs>/)
          if (docsMatch) {
            const docsContent = docsMatch[1].trim()
            
            // 将文档内容注入到组件中
            return code.replace(
              '</script>',
              `
// 自动生成的文档
const __docs = ${JSON.stringify(docsContent)}
defineExpose({ __docs })
</script>`
            )
          }
        }
      }
    }
  ]
})

// 3. 组件示例 (使用自定义块)
// MyComponent.vue
<template>
  <div class="my-component">{{ message }}</div>
</template>

<script setup>
import { ref } from 'vue'

const message = ref('Hello')
</script>

<docs>
# 组件文档
这是一个示例组件,用于展示自定义块功能。

## Props
- `prop1`: 说明...
- `prop2`: 说明...

## 事件
- `@change`: 说明...
</docs>

<style>
.my-component {
  /* 样式 */
}
</style>

39. Vue3与微前端架构集成方案

答案

  • 微前端架构允许多个独立开发的前端应用在一个页面上共存
  • Vue3可以作为主应用或子应用集成到微前端架构
  • 常用框架:single-spa、qiankun、micro-app等
  • 需要处理生命周期、样式隔离、通信、路由协调等问题

最佳实践

// 1. 使用qiankun集成Vue3应用

// 主应用(main.js)
import { createApp } from 'vue'
import { registerMicroApps, start } from 'qiankun'
import App from './App.vue'

// 创建Vue主应用
createApp(App).mount('#main-app')

// 注册微应用
registerMicroApps([
  {
    name: 'vue3-sub-app',
    entry: '//localhost:8081', // 子应用入口地址
    container: '#sub-app-container', // 容器节点
    activeRule: '/sub-app', // 激活规则
    props: {
      // 传递给子应用的数据
      initData: {
        user: 'admin'
      },
      // 主应用暴露给子应用的方法
      onEvent: (data) => console.log('子应用事件:', data)
    }
  }
])

// 启动qiankun
start()

// 2. Vue3子应用配置
// 子应用入口文件
import { createApp } from 'vue'
import App from './App.vue'

let app = null
let router = null

// qiankun生命周期钩子
window.__POWERED_BY_QIANKUN__ ? import('./public-path') : null

export async function bootstrap() {
  console.log('Vue3子应用启动中');
}

export async function mount(props) {
  // 获取主应用传递的数据
  const { container, initData, onEvent } = props
  
  // 创建Vue实例
  app = createApp(App)
  
  // 提供主应用数据和方法
  app.provide('parentData', initData)
  app.provide('parentEvent', onEvent)
  
  // 挂载到指定容器
  app.mount(container ? container.querySelector('#app') : '#app')
}

export async function unmount() {
  // 卸载应用
  app?.unmount()
  app = null
}

// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
  createApp(App).mount('#app')
}

// 3. 子应用使用主应用数据
<script setup>
import { inject } from 'vue'

// 获取主应用数据和方法
const parentData = inject('parentData', {})
const parentEvent = inject('parentEvent', () => {})

function sendMessage() {
  // 调用主应用方法
  parentEvent({
    type: 'MESSAGE',
    data: 'Hello from sub app'
  })
}
</script>

40. Vue3与WebAssembly集成方案

答案

  • WebAssembly(WASM)是一种低级字节码格式,性能接近原生
  • Vue3可以与WASM模块集成,实现性能敏感操作
  • 常见用途:图像处理、音频分析、物理计算、大数据处理
  • 通过JS Bridge与Vue3响应式系统交互

最佳实践

<script setup>
import { ref, onMounted, reactive } from 'vue'

// WASM模块状态
const wasmModule = ref(null)
const processing = ref(false)
const result = ref(null)
const error = ref(null)

// 图像处理状态
const imageData = reactive({
  width: 0,
  height: 0,
  data: null
})

// 加载WASM模块
async function loadWasmModule() {
  try {
    // 导入WASM模块
    const module = await import('../wasm/image_processor')
    // 初始化WASM
    await module.default() // 等待WASM初始化
    wasmModule.value = module
    console.log('WASM模块已加载')
  } catch (err) {
    error.value = '加载WASM模块失败: ' + err.message
  }
}

// 使用WASM处理图像
async function processImage(file) {
  if (!wasmModule.value) {
    error.value = 'WASM模块未加载'
    return
  }
  
  try {
    processing.value = true
    
    // 1. 读取图像文件
    const bitmap = await createImageBitmap(file)
    imageData.width = bitmap.width
    imageData.height = bitmap.height
    
    // 2. 创建Canvas获取像素数据
    const canvas = document.createElement('canvas')
    canvas.width = bitmap.width
    canvas.height = bitmap.height
    const ctx = canvas.getContext('2d')
    ctx.drawImage(bitmap, 0, 0)
    const imgData = ctx.getImageData(0, 0, bitmap.width, bitmap.height)
    
    // 3. 创建输入内存
    const { memory } = wasmModule.value
    const inputPtr = wasmModule.value._malloc(imgData.data.length)
    const inputHeap = new Uint8Array(memory.buffer, inputPtr, imgData.data.length)
    inputHeap.set(new Uint8Array(imgData.data))
    
    // 4. 调用WASM函数处理图像
    // 例如:应用高斯模糊
    const outputPtr = wasmModule.value._applyGaussianBlur(
      inputPtr, 
      bitmap.width, 
      bitmap.height, 
      5.0 // sigma
    )
    
    // 5. 从WASM内存获取结果
    const outputHeap = new Uint8Array(
      memory.buffer, 
      outputPtr, 
      bitmap.width * bitmap.height * 4
    )
    
    // 6. 创建处理后的ImageData
    const outputImgData = new ImageData(
      new Uint8ClampedArray(outputHeap), 
      bitmap.width, 
      bitmap.height
    )
    
    // 7. 把处理后的图像绘制到Canvas
    ctx.putImageData(outputImgData, 0, 0)
    result.value = canvas.toDataURL()
    
    // 8. 释放WASM内存
    wasmModule.value._free(inputPtr)
    wasmModule.value._free(outputPtr)
  } catch (err) {
    error.value = '处理图像失败: ' + err.message
  } finally {
    processing.value = false
  }
}

// 加载WASM模块
onMounted(() => {
  loadWasmModule()
})
</script>

<template>
  <div>
    <h1>WebAssembly图像处理</h1>
    
    <div v-if="error" class="error">{{ error }}</div>
    
    <input 
      type="file" 
      accept="image/*"
      @change="e => e.target.files[0] && processImage(e.target.files[0])"
      :disabled="!wasmModule || processing" 
    />
    
    <div v-if="processing" class="processing">处理中...</div>
    
    <div v-if="result" class="result">
      <h3>处理结果</h3>
      <img :src="result" alt="处理后的图像" />
    </div>
  </div>
</template>

Vue3 项目最佳设计方案

小型项目设计方案

1. 项目结构

src/
├── assets/                # 静态资源
├── components/            # 通用组件
├── composables/           # 组合式函数 
├── views/                 # 页面组件
├── App.vue                # 根组件
├── main.js                # 入口文件
└── router.js              # 路由配置

2. 状态管理

// 使用 provide/inject 轻量级状态管理
// store/counter.js
import { reactive, readonly, provide, inject } from 'vue'

const stateSymbol = Symbol('counter')

export function provideCounter() {
  const state = reactive({
    count: 0
  })
  
  function increment() {
    state.count++
  }
  
  provide(stateSymbol, {
    state: readonly(state),
    increment
  })
}

export function useCounter() {
  const counter = inject(stateSymbol)
  if (!counter) throw new Error('未找到Counter状态')
  return counter
}

3. 功能封装

// composables/useFetch.js
import { ref, reactive, onMounted } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)
  const loading = ref(false)
  
  async function fetchData() {
    loading.value = true
    try {
      const res = await fetch(url)
      data.value = await res.json()
    } catch (err) {
      error.value = err
    } finally {
      loading.value = false
    }
  }
  
  onMounted(fetchData)
  
  return { data, error, loading, refetch: fetchData }
}

中型项目设计方案

1. 项目结构

src/
├── assets/                # 静态资源
├── components/            # 通用组件
│   ├── common/            # 基础UI组件
│   └── business/          # 业务组件
├── composables/           # 组合式函数
├── router/                # 路由配置
│   ├── index.js           # 路由注册
│   └── routes/            # 路由模块
├── services/              # API服务
├── stores/                # Pinia状态管理
├── utils/                 # 工具函数
├── views/                 # 页面组件
└── main.js                # 入口文件

2. 状态管理 (Pinia)

// stores/user.js
import { defineStore } from 'pinia'
import { userService } from '@/services/user'

export const useUserStore = defineStore('user', {
  state: () => ({
    profile: null,
    roles: [],
    isLoggedIn: false,
    loading: false
  }),
  
  getters: {
    isAdmin: (state) => state.roles.includes('admin')
  },
  
  actions: {
    async login(username, password) {
      this.loading = true
      try {
        const { user, token } = await userService.login(username, password)
        this.profile = user
        this.roles = user.roles
        this.isLoggedIn = true
        localStorage.setItem('token', token)
      } finally {
        this.loading = false
      }
    },
    
    logout() {
      this.profile = null
      this.roles = []
      this.isLoggedIn = false
      localStorage.removeItem('token')
    }
  }
})

3. API层封装

// services/http.js
import axios from 'axios'

const http = axios.create({
  baseURL: '/api',
  timeout: 10000
})

http.interceptors.request.use(config => {
  const token = localStorage.getItem('token')
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
})

http.interceptors.response.use(
  response => response.data,
  error => {
    // 全局错误处理
    if (error.response?.status === 401) {
      // 处理未授权
    }
    return Promise.reject(error)
  }
)

export default http

// services/user.js
import http from './http'

export const userService = {
  login(username, password) {
    return http.post('/auth/login', { username, password })
  },
  
  getProfile() {
    return http.get('/user/profile')
  }
}

大型项目设计方案

1. 项目结构 (基于特性的模块化)

src/
├── assets/                 # 静态资源
├── components/             # 全局通用组件
├── composables/            # 全局组合式函数
├── config/                 # 全局配置
├── modules/                # 功能模块
│   ├── auth/               # 认证模块
│   │   ├── components/     # 模块组件
│   │   ├── composables/    # 模块组合式函数
│   │   ├── views/          # 模块页面
│   │   ├── services/       # 模块API
│   │   ├── store/          # 模块状态
│   │   └── routes.js       # 模块路由
│   ├── dashboard/          # 仪表盘模块
│   ├── orders/             # 订单模块
│   └── products/           # 产品模块
├── router/                 # 路由配置
├── services/               # 全局API服务
├── stores/                 # 全局状态
├── utils/                  # 工具函数
│   ├── directives/         # 自定义指令
│   ├── filters/            # 过滤器
│   └── plugins/            # 插件
├── App.vue                 # 根组件
└── main.js                 # 入口文件

2. 高级状态管理

// modules/products/store/products.js
import { defineStore } from 'pinia'
import { productsService } from '../services/products'

export const useProductsStore = defineStore('products', {
  state: () => ({
    items: [],
    categories: [],
    filters: {
      categoryId: null,
      price: { min: 0, max: null },
      sortBy: 'name'
    },
    pagination: {
      page: 1,
      limit: 20,
      total: 0
    },
    loading: false
  }),
  
  getters: {
    filteredProducts(state) {
      // 复杂过滤逻辑
      return state.items.filter(/* ... */)
    }
  },
  
  actions: {
    async fetchProducts() {
      this.loading = true
      try {
        const { data, pagination } = await productsService.getProducts({
          ...this.filters,
          page: this.pagination.page,
          limit: this.pagination.limit
        })
        
        this.items = data
        this.pagination.total = pagination.total
      } finally {
        this.loading = false
      }
    },
    
    setFilter(key, value) {
      this.filters[key] = value
      this.pagination.page = 1 // 重置页码
      return this.fetchProducts()
    }
  }
})

3. 模块化路由

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'

// 导入模块路由
import authRoutes from '@/modules/auth/routes'
import dashboardRoutes from '@/modules/dashboard/routes'
import productsRoutes from '@/modules/products/routes'
import ordersRoutes from '@/modules/orders/routes'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', redirect: '/dashboard' },
    ...authRoutes,
    ...dashboardRoutes,
    ...productsRoutes,
    ...ordersRoutes,
    { path: '/:pathMatch(.*)*', component: () => import('@/views/NotFound.vue') }
  ]
})

// 全局导航守卫
router.beforeEach((to, from, next) => {
  const requiresAuth = to.matched.some(record => record.meta.requiresAuth)
  const isLoggedIn = localStorage.getItem('token')
  
  if (requiresAuth && !isLoggedIn) {
    next('/auth/login')
  } else {
    next()
  }
})

export default router

// modules/products/routes.js
export default [
  {
    path: '/products',
    component: () => import('./views/ProductLayout.vue'),
    meta: { requiresAuth: true },
    children: [
      {
        path: '',
        name: 'products-list',
        component: () => import('./views/ProductsList.vue')
      },
      {
        path: ':id',
        name: 'product-detail',
        component: () => import('./views/ProductDetail.vue'),
        props: true
      }
    ]
  }
]

通用最佳实践

1. API请求与响应处理

// composables/useApi.js
import { ref, unref } from 'vue'

export function useApi(apiFn) {
  const data = ref(null)
  const error = ref(null)
  const loading = ref(false)
  
  async function execute(...args) {
    loading.value = true
    error.value = null
    
    try {
      // 支持传入ref作为参数
      const resolvedArgs = args.map(arg => unref(arg))
      data.value = await apiFn(...resolvedArgs)
      return data.value
    } catch (err) {
      error.value = err
      throw err
    } finally {
      loading.value = false
    }
  }
  
  return {
    data,
    error,
    loading,
    execute
  }
}

// 使用示例
const { data: products, loading, execute: fetchProducts } = useApi(productsService.getProducts)

// 调用API
await fetchProducts({ category: 'electronics' })

2. 高可复用组件设计

<!-- BaseTable.vue -->
<script setup>
import { computed } from 'vue'

const props = defineProps({
  items: {
    type: Array,
    default: () => []
  },
  columns: {
    type: Array,
    default: () => []
  },
  selectable: Boolean
})

const emit = defineEmits(['row-click', 'selection-change'])

const selectedRows = ref([])

const hasActions = computed(() => {
  return !!slots.actions
})

// ...其他逻辑
</script>

<template>
  <div class="base-table">
    <table>
      <thead>
        <tr>
          <th v-if="selectable">
            <input type="checkbox" @change="toggleSelectAll" />
          </th>
          <th v-for="col in columns" :key="col.key">
            {{ col.title }}
          </th>
          <th v-if="hasActions">操作</th>
        </tr>
      </thead>
      
      <tbody>
        <tr 
          v-for="(item, index) in items" 
          :key="item.id || index"
          @click="$emit('row-click', item)"
        >
          <td v-if="selectable">
            <input 
              type="checkbox"
              :checked="isSelected(item)"
              @change="toggleSelect(item)"
            />
          </td>
          
          <td v-for="col in columns" :key="col.key">
            <!-- 支持自定义单元格渲染 -->
            <slot :name="`cell-${col.key}`" :item="item" :value="item[col.key]">
              {{ item[col.key] }}
            </slot>
          </td>
          
          <td v-if="hasActions">
            <slot name="actions" :item="item"></slot>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<!-- 使用示例 -->
<template>
  <BaseTable
    :items="products"
    :columns="columns"
    selectable
    @row-click="handleRowClick"
    @selection-change="handleSelectionChange"
  >
    <!-- 自定义单元格渲染 -->
    <template #cell-price="{ value }">
      {{ formatCurrency(value) }}
    </template>
    
    <!-- 自定义操作列 -->
    <template #actions="{ item }">
      <button @click.stop="editProduct(item)">编辑</button>
      <button @click.stop="deleteProduct(item)">删除</button>
    </template>
  </BaseTable>
</template>

3. 权限控制系统

<!-- 权限控制指令 -->
<script>
// directives/permission.js
import { useUserStore } from '@/stores/user'

export const vPermission = {
  mounted(el, binding) {
    const { value } = binding
    const userStore = useUserStore()
    
    if (value && !hasPermission(userStore, value)) {
      el.parentNode?.removeChild(el)
    }
  }
}

function hasPermission(userStore, permission) {
  if (Array.isArray(permission)) {
    return permission.some(p => userStore.permissions.includes(p))
  }
  return userStore.permissions.includes(permission)
}
</script>

<!-- 权限控制组件 -->
<script setup>
// components/Permission.vue
import { useUserStore } from '@/stores/user'

const props = defineProps({
  permission: {
    type: [String, Array],
    required: true
  },
  redirectTo: {
    type: String,
    default: ''
  }
})

const userStore = useUserStore()

function checkPermission() {
  if (Array.isArray(props.permission)) {
    return props.permission.some(p => userStore.permissions.includes(p))
  }
  return userStore.permissions.includes(props.permission)
}

const hasPermission = computed(() => checkPermission())
</script>

<template>
  <slot v-if="hasPermission"></slot>
  <slot v-else name="fallback"></slot>
</template>

<!-- 使用示例 -->
<template>
  <!-- 使用指令 -->
  <button v-permission="'edit:products'">编辑产品</button>
  
  <!-- 使用组件 -->
  <Permission permission="edit:products">
    <div>有权限时显示的内容</div>
    <template #fallback>
      <div>无权限时的提示</div>
    </template>
  </Permission>
</template>

4. 大型应用性能优化

// 1. 组件懒加载
const routes = [
  {
    path: '/dashboard',
    component: () => import('@/modules/dashboard/views/Dashboard.vue')
  }
]

// 2. 列表虚拟滚动
import { useVirtualList } from '@vueuse/core'

const { list, containerProps, wrapperProps } = useVirtualList(
  data,
  {
    itemHeight: 60,
    overscan: 10
  }
)

// 3. 多级缓存设计
// views/DataView.vue
<script setup>
import { useQuery } from '@/composables/useQuery'

const props = defineProps({
  queryKey: String,
  queryFn: Function
})

// 自动缓存查询结果
const { data, loading } = useQuery(
  () => props.queryKey,
  () => props.queryFn(),
  {
    cacheTime: 5 * 60 * 1000, // 缓存5分钟
    staleTime: 60 * 1000 // 1分钟内不重新请求
  }
)
</script>

5. 环境配置与构建优化

// 1. 环境变量设计
// .env.development
VUE_APP_API_URL=https://dev-api.example.com
VUE_APP_FEATURE_FLAGS={"newUI":true,"analytics":false}

// .env.production
VUE_APP_API_URL=https://api.example.com
VUE_APP_FEATURE_FLAGS={"newUI":true,"analytics":true}

// 2. 动态读取环境配置
// config/index.js
const featureFlags = JSON.parse(import.meta.env.VUE_APP_FEATURE_FLAGS || '{}')

export const config = {
  apiUrl: import.meta.env.VUE_APP_API_URL,
  features: {
    newUI: featureFlags.newUI ?? false,
    analytics: featureFlags.analytics ?? false
  }
}

// 3. 构建优化 (vite.config.js)
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { visualizer } from 'rollup-plugin-visualizer'

export default defineConfig({
  plugins: [
    vue(),
    visualizer({ open: true }) // 分析打包体积
  ],
  build: {
    target: 'es2015',
    minify: 'terser',
    cssCodeSplit: true,
    rollupOptions: {
      output: {
        manualChunks: {
          'vue-vendor': ['vue', 'vue-router', 'pinia'],
          'ui-lib': ['element-plus'], // UI库单独打包
          'chart': ['echarts', 'chart.js']
        }
      }
    }
  }
})