vue3基础知识总结

119 阅读9分钟

一、基础与核心差异

  1. Vue3 相比 Vue2 有哪些核心改进?
    • 响应式系统:基于 Proxy 实现,支持动态新增/删除属性、数组索引修改,无需 $set;Vue2 基于 Object.defineProperty,有较多限制。
    • API 风格:主推组合式 API(setupref 等),按功能聚合逻辑;Vue2 用选项式 API,逻辑分散在多个选项中。
    • 性能优化:编译时优化(静态提升、PatchFlags)、响应式懒代理,渲染性能提升约55%。
    • 生态升级:支持 TypeScript 原生集成,路由(Vue Router 4)、状态管理(Pinia)配套更新,移除 filter$on 等冗余特性。
  2. 选项式 API 与组合式 API 的区别及适用场景?
    • 选项式 API:按 datamethodsmounted 等选项分割逻辑,适合简单组件,上手快;但复杂组件中逻辑分散,复用困难。
    • 组合式 API:用 setup 和组合函数按功能聚合逻辑,适合复杂组件,逻辑复用性强;学习成本稍高,但可维护性更好。 - 场景:小型项目/简单组件用选项式;中大型项目/复杂组件(如表单、数据看板)用组合式。
  3. Vue3 为什么移除了 filter$on/$off 等特性?
    • filter:功能可被 computed 或方法替代,且 filter 不支持参数类型推断,与组合式 API 风格不符。
    • $on/$off(事件总线):易导致全局事件混乱,难以追踪;推荐用第三方库(如 mitt)或 Pinia 实现跨组件通信,更可控。
  4. Vue3 的 template 语法有哪些新增特性?
    • 多根节点:组件模板可直接包含多个根节点(无需外层包裹 div)。
    • 动态指令参数:支持动态绑定指令参数(如 v-bind:[key]="value"v-on:[event]="handler")。
    • 片段(Fragments):减少不必要的 DOM 嵌套,优化渲染性能。
    • 更好的 TypeScript 支持:模板表达式类型校验更严格。
  5. Vue3 对 TypeScript 的支持做了哪些优化?
    • 核心代码用 TypeScript 重写,类型定义更完善。
    • 组合式 API 中变量/函数为显式局部变量,TypeScript 可直接推断类型,无需额外声明。
    • defineProps/defineEmits 支持泛型定义,类型校验更精准(如 defineProps<{ name: string }>())。
    • 模板中表达式的类型错误可在编译时被捕获。

二、响应式系统

  1. refreactivetoReftoRefs 的区别及使用场景?
    • ref:包装基础类型(number/string 等)为响应式,也可包装对象;需通过 .value 访问,模板中自动解包。场景:基础类型、需单独维护的响应式变量。
    • reactive:将对象/数组转为响应式(深层代理),直接访问属性(无需 .value)。场景:复杂对象/数组(如 user: { name, age })。
    • toRef:为 reactive 对象的单个属性创建 ref(保持响应性)。场景:需单独传递对象的某个属性(如 const ageRef = toRef(user, 'age'))。
    • toRefs:将 reactive 对象转为 ref 集合(所有属性变为 ref)。场景:解构 reactive 对象时避免丢失响应性(如 const { name, age } = toRefs(user))。
  2. Vue3 响应式基于 Proxy,相比 Vue2 的 Object.defineProperty 有哪些优势?
    • 监听范围更广:Proxy 代理整个对象,支持动态新增/删除属性(如 obj.newKey = 1delete obj.key),无需 $setObject.defineProperty 只能监听初始化时的属性。
    • 数组支持更完善:Proxy 可拦截数组索引修改(arr[0] = 1)、长度修改(arr.length = 0);Object.defineProperty 需重写数组原型方法(push/splice 等),有局限性。
    • 性能更优:Proxy 采用“懒代理”,访问嵌套对象时才递归代理,初始化性能更好;Object.defineProperty 需递归遍历所有属性,损耗大。
    • 拦截器更全面:支持 deletePropertyhas 等13种拦截操作,覆盖更多场景。
  3. 什么情况下响应式会失效?如何解决?
    • 直接替换响应式对象let obj = reactive({ a: 1 }); obj = { b: 2 }(原代理对象被替换,新对象非响应式)。解决:用 ref 包装(obj.value = { b: 2 })。
    • 通过索引修改数组长度const arr = reactive([1,2]); arr.length = 0(Vue3 已支持,低版本可能失效)。解决:用 splicearr.splice(0))。
    • 解构 reactive 对象const { a } = reactive({ a: 1 })a 变为普通值,非响应式)。解决:用 toRefs 解构(const { a } = toRefs(...))。
  4. shallowRefshallowReactivemarkRaw 的作用及使用场景?
    • shallowRef:浅层响应式,仅 .value 变化触发更新(内部对象修改不触发)。场景:无需深层响应的大对象(如图表配置)。
    • shallowReactive:浅层响应式,仅顶层属性变化触发更新(嵌套对象修改不触发)。场景:已知深层数据不会变化的对象(如静态配置)。
    • markRaw:标记对象为“非响应式”,避免被 reactive/ref 包装。场景:第三方库实例(如 echarts 实例)、无需响应式的纯数据。
  5. ref.value 设计原理是什么?为什么模板中不需要 .value
    • 原理ref 通过包装对象({ value: 原始值 })实现响应式(基础类型无法被 Proxy 直接代理),.value 是访问原始值的入口。
    • 模板自动解包:Vue3 编译模板时,会自动识别 ref 变量,在渲染时隐式添加 .value(如 {{ count }} 等价于 count.value),简化写法。
  6. 如何手动触发响应式更新?
    • triggerRef(ref):手动触发 shallowRef 的更新(当修改其内部对象时)。
    • componentInstance.$forceUpdate():强制组件重新渲染(不推荐,可能导致性能问题)。
    • 替换 ref.valueref.value = { ...ref.value }(触发 set 拦截器)。

三、组合式 API 与生命周期

  1. setup 的执行时机?为什么没有 this
    • 执行时机:在 beforeCreate 之前执行,此时组件实例未初始化,datamethodsprops 等尚未挂载。
    • this 的原因
      • 避免 this 指向模糊(Vue2 中 this 常因嵌套函数丢失上下文)。
      • 促进逻辑复用(组合函数无需依赖组件实例,可独立运行)。
      • 更好支持 TypeScript(this 类型难以自动推断,显式变量更易类型化)。
  2. Vue3 生命周期钩子与 Vue2 如何对应?onRenderTracked 有什么用?
    • 对应关系:
      • onBeforeMountbeforeMount
      • onMountedmounted
      • onBeforeUpdatebeforeUpdate
      • onUpdatedupdated
      • onBeforeUnmountbeforeDestroy
      • onUnmounteddestroyed
    • onRenderTracked:调试用,当组件渲染过程中收集依赖时触发,可查看哪些响应式数据被追踪。
  3. watchwatchEffect 的区别?如何监听 reactive 对象的某个属性?
    • 区别
      • watch 需明确指定监听源(如 ref/reactive 属性),可获取新旧值,支持 deep/immediate
      • watchEffect 自动追踪依赖,无需指定源,初始化时立即执行,无法获取旧值。
    • 监听 reactive 对象的单个属性:需用 getter 函数(避免监听整个对象):
    watch(() => obj.name, (newVal) => { ... }) 
    
  4. computed 的缓存机制?如何实现可写的 computed
    • 缓存机制computed 会缓存计算结果,仅当依赖的响应式数据变化时,才重新计算;多次访问相同值时直接返回缓存。
    • 可写 computed:通过 get/set 配置实现:
    const fullName = computed({ 
        get: () => `${first.value} ${last.value}`, 
        set: (val) => { 
            const [f, l] = val.split(' '); 
            first.value = f; 
            last.value = l; 
        } 
     }); 
    
  5. 组合函数(Composables)的设计原则?如何封装一个复用性高的组合函数?
    • 设计原则
      • 单一职责:一个组合函数只处理一个功能(如 useFetch 仅处理请求)。
      • 显式输入输出:通过参数接收依赖,通过返回值暴露状态/方法,不依赖 this
      • 无副作用:内部状态不污染全局,清理逻辑需完善(如 onBeforeUnmount 取消请求)。
    • 封装示例
    // useFetch.js 
    export function useFetch(url) { 
        const data = ref(null); 
        const loading = ref(true); 
        const fetchData = async () => { 
            loading.value = true; 
            data.value = await axios.get(url); 
            loading.value = false; 
        }; 
        fetchData(); 
        onBeforeUnmount(() => { /* 取消请求 */ }); 
        return { data, loading, fetchData }; 
    } 
    
  6. <script setup> 相比普通 setup 函数有哪些语法简化?
    • 无需显式定义 setup 函数,顶层代码自动在 setup 中执行。
    • 无需 return,顶层变量、函数、导入内容自动暴露给模板。
    • 内置 defineProps/defineEmits/defineExpose 宏,无需导入。
    • 支持直接导入组件并使用(无需在 components 选项注册)。

四、组件通信与状态管理

  1. Vue3 中父子组件通信的方式有哪些?defineProps/defineEmits 如何使用?
    • 方式props + emitv-modelref 访问子组件、provide/inject(跨代)。
    • defineProps(子组件接收 props):
    <script setup> 
        const props = defineProps({ name: String, age: { type: Number, default: 0 } }); 
    </script> 
    
    • defineEmits(子组件触发事件):
    <script setup> 
        const emit = defineEmits(['update', 'delete']); 
        const handleClick = () => emit('update', 'newVal'); 
    </script> 
    
  2. v-model 在 Vue3 中的变化?如何实现多值绑定和自定义修饰符?
    • 变化:Vue3 中 v-model 等价于 :modelValue + @update:modelValue(替代 Vue2 的 :value + @input)。
    • 多值绑定:通过 v-model:propName 绑定多个值:
        <Child v-model:name="name" v-model:age="age" /> 
    
    • 自定义修饰符:子组件通过 props.modelModifiers 接收修饰符:
    <!-- 父组件 --> 
    <Child v-model.capitalize="name" /> 
    <!-- 子组件 --> 
    <script setup> 
        const props = defineProps({ 
            modelValue: String, 
            modelModifiers: { default: () => ({}) } 
        }); 
        const emit = defineEmits(['update:modelValue']); 
        // 使用修饰符:if (props.modelModifiers.capitalize) 处理大写 
    </script> 
    
  3. provide/inject 如何实现响应式传递?使用时需要注意什么?
    • 响应式传递provide 传递 ref/reactive 对象,inject 接收后可直接响应变化:
    // 父组件 
    const theme = ref('light'); 
    provide('theme', theme); 
    // 子组件 
    const theme = inject('theme'); // 响应式 
    
    • 注意事项
      • 避免在子组件中直接修改 inject 的值(破坏单向数据流),应通过 emit 通知父组件修改。
      • 需指定默认值时,用 inject('key', defaultValue)
  4. Pinia 相比 Vuex 有哪些优势?如何用 Pinia 实现模块化状态管理?
    • 优势
      • 无需 mutations,直接通过 actions 修改状态(简化代码)。
      • 移除 modules,用 defineStore 定义独立仓库,天然支持模块化。
      • 原生支持 TypeScript,类型推断更友好。 - 支持热更新和调试工具集成。
    • 模块化示例
    // stores/user.js 
    import { defineStore } from 'pinia'; 
    export const useUserStore = defineStore('user', { 
        state: () => ({ name: '张三' }), 
        actions: { setName(newName) { this.name = newName; } } 
    }); 
    // 组件中使用 
    const userStore = useUserStore(); 
    
  5. 非父子组件通信有哪些方案?
    • 事件总线:用 mitt 库(Vue3 移除了 $on/$off),适合简单场景。
    • provide/inject:适合祖孙组件,跨层级传递数据。
    • Pinia:全局状态管理,适合复杂应用(多组件共享状态)。
    • 路由参数:通过 URL 传递数据(适合页面级组件)。

五、性能优化与实战

  1. Vue3 做了哪些编译时优化?
    • 静态提升:将静态节点/属性(如 <div class="static">)提升到渲染函数外,避免每次渲染重新创建。
    • PatchFlags:为动态节点添加标记(如 TEXT/CLASS),更新时只遍历带标记的节点,减少对比开销。
    • 缓存事件处理函数@click="handleClick" 会缓存函数引用,避免每次渲染创建新函数导致子组件更新。
    • 预字符串化:连续静态节点会被合并为字符串,减少虚拟 DOM 创建成本。
  2. 如何避免组件不必要的重渲染?
    • shallowRef/shallowReactive 处理无需深层响应的数据。
    • memo 包装组件,指定依赖项(仅依赖变化时重新渲染):
      <script setup> 
          import { memo } from 'vue'; 
          const MyComponent = memo(
              ({ prop1 }) => <div>{prop1}</div>, 
              (prev, next) => prev.prop1 === next.prop1
          ); 
      </script> 
      
    • 避免在模板中使用匿名函数(如 @click="() => {}"),会导致每次渲染创建新函数。
    • toRef/toRefs 解构 reactive 对象,保持响应性的同时减少重渲染触发。
  3. 大列表渲染如何优化?
    • 虚拟滚动:用 vue-virtual-scroller 等库,只渲染可视区域内的列表项(适合1000+数据)。
    • v-forkeykey 需唯一且稳定(避免用索引),帮助 Vue 精准复用节点。
    • 列表分页:分段加载数据,减少单次渲染压力。
    • 冻结静态内容:用 Object.freeze 冻结不变化的数据,避免响应式追踪开销。
  4. 如何在 Vue3 中实现按需加载组件?
    • 异步组件:用 defineAsyncComponent 定义,配合 import() 动态加载:
        import { defineAsyncComponent } from 'vue'; 
        const AsyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue')); 
    
    • Suspense 配合:在等待异步组件加载时显示占位内容:
    <Suspense> 
        <template #default><AsyncComponent /></template> 
        <template #fallback>加载中...</template> 
     </Suspense> 
    

六、高级特性与生态

  1. TeleportSuspense 的使用场景?
    • Teleport:将组件内容渲染到指定 DOM 节点(如 body),解决嵌套组件的样式隔离问题(如弹窗、模态框避免被父组件 overflow: hidden 截断):
    <Teleport to="body"><div class="modal">弹窗</div></Teleport> 
    
    • Suspense:配合异步组件或 async setup,在等待异步操作(如接口请求)时显示加载状态,简化异步逻辑(注意:Suspense 仍为实验性特性,生产环境需谨慎)。
  2. Vue Router 4 相比 Vue Router 3 有哪些变化?
    • 支持组合式 API:新增 useRouter/useRoute 钩子,替代 this.$router/this.$route
    • 路由守卫可直接使用组合式 API(如 onBeforeRouteLeave)。
    • 移除 mode 选项,用 createWebHistory/createWebHashHistory 替代。
    • 支持路由组件懒加载的更简洁写法:component: () => import('./Page.vue')
  3. 如何在 Vue3 中集成单元测试?
    • Vitest 作为测试框架(Vue 官方推荐,速度快,支持 ESM)。
    • 配合 @vue/test-utils 测试组件:
    import { mount } from '@vue/test-utils'; 
    import MyComponent from './MyComponent.vue'; 
    test('renders props.name', () => { 
        const wrapper = mount(MyComponent, { props: { name: 'test' } }); 
        expect(wrapper.text()).toContain('test'); 
    }); 
    
  4. Vue2 项目迁移到 Vue3 的核心步骤及注意事项?
    • 依赖升级:Vue 升级到 3.x,Vue Router 升级到 4.x,用 Pinia 替代 Vuex。
    • 语法迁移
      • data 函数 → ref/reactive
      • 生命周期钩子 → 组合式 API 钩子(如 mountedonMounted)。
      • this.xxx → 直接访问变量,this.$emitdefineEmits
    • 移除特性处理filtercomputed 替代,$childrenref 获取子组件。
    • 兼容性:第三方库需支持 Vue3(如 element-plus 替代 element-ui),IE 浏览器不再支持(Vue3 基于 ES6+)。