面试备战录

56 阅读4分钟

1、Vue3 中computedwatchwatchEffect区别

答:computedwatchwatchEffect都是用于监听响应式数据变化的工具,但他们的用途、机制和适应场景各不相同。

  • computed:计算属性API,基于响应式计算出一个新的值并具备缓存功能,只有当依赖的响应式数据发生变化时才会重新计算。
    • 具备缓存,只有依赖变化时才会重新计算;
    • 适用于复杂模板中的派生状态
    • 通常用作模板展示的变量或派生逻辑
  • watch:侦听器API,用于侦听一个或多个响应式数据的变化,当值发生变化时,执行指定的副作用函数。
  • watchEffect:Vue提供的一个自动收集响应式副作用的函数,不需要手动指定依赖,Vue会自动追踪内部使用的响应式数据,并在变化时重新运行这个副作用。
    • 自动收集其内部的访问的所有响应式依赖
    • 立即执行一次
    • 适用于快速编写响应副作用的逻辑,无需明确指定依赖
    • 更简洁,但不适合复杂的逻辑场景

2、Tree-shaking 是什么

答:Tree-shaking(摇掉未使用代码)是一种构建时优化技术,自动移除未使用的模块和代码,让最终打包体积更小,性能更好。 Tree-shaking 的关键条件:

  • 必须使用ES Module(import/export),CommonJS不支持;
  • 必须是纯函数无副作用,函数必须不依赖外部、无副作用;
  • 打包工具支持,Vite、Webpack 2+ 都支持;
  • package.json中配置 "sideEffects": false(告诉打包器:我这个包是纯净的,可以 tree-shake)

注:Vue3中的源码使用的就是模块编写的,比如你可以只导入用到的模块import { ref, reactive } from 'vue',所以Vue3的打包器可以只打包你用到的这些函数,不会把整个Vue都引入,从而可以减小打包后的体积。

3、如何优化 Vue 组件之间的通信效率

  • 降低响应式依赖颗粒度,Vue的响应式是按属性依赖收集的,依赖越多,更新越频繁。
// ❌ 不推荐:整个对象都是响应式,子组件使用一个字段也会被影响
const form = reactive({ name: '', age: 0, address: '' });
// ✅ 推荐:拆分响应式粒度
const name = ref('');
const age = ref(0);
  • 使用shallowReactive/shallowRef减少深层响应式监听(watch 的深度监听),适用于不频繁更新的结构化对象,避免Watch Tree深层依赖收集导致性能下降。
// shallowReactive 只会把对象第一次变成响应式的,shallowRef 只追踪 .value 的变动,不递归内部结构
const state = shallowReactive({ config: deepConfigObject });
watch(
  () => state,
  (newVal, oldVal) => console.log('newVal', newVal);,
  { deep: true;}  //即使添加了deep:true; 也不会不监听 config 内部结构
);
  • 使用v-memomarkRaw避免不必要的响应式追踪(Vue3)
<!-- v-memo 用于模板,缓存 v-for 内容,只有当 item.isFrozen 改变时才会重新渲染-->
<MyCard v-for="item in items" :key="item.id" :data="item" v-memo="[item.isFrozen]" />
// markRaw:跳过响应式包装 -  也就是markRaw包裹的对象在放在reactive中,就不会对内部属性变成响应式的了
import { reactive, markRaw } from 'vue';

const rawData = markRaw({
  bigData: [
    /* 很多数据 */
  ],
  someInstance: new SomeClass()
});

const state = reactive({
  data: rawData
});
  • 避免props传对象,导致函数频繁更新
<!-- ❌ 每次父组件更新,obj 是新引用,子组件会重新渲染 -->
<MyComp :config="{ a: 1 }" />
<!-- React 中可以使用useCallback Vue中最好把 const config = reactive({ a: 1 })可以保证引用地址不变  -->
  • 使用 defineExpose()、ref() 提供更直接的通信,子组件暴露方法,父组件通过 ref 调用,代替大量 props/emit 的传递
<!-- 子组件 -->
<script setup>
function focusInput() {
  inputRef.value?.focus();
}
// 暴露方法给父组件
defineExpose({
  focusInput
});
</script>
<!-- 父组件 -->
<script setup>
const childRef = ref();

const handleClick = () => {
  childRef.value?.focusInput();
};
</script>

<template>
  <MyChild ref="childRef" />
</template>
  • 拆分大型组件,按需渲染;避免「巨型组件」造成的性能瓶颈:
    • 拆分为功能组件 + 逻辑组件
    • 使用 <Suspense>defineAsyncComponent() 异步加载子组件
    • 搭配 <keep-alive> 缓存部分组件,提升切换性能
  • 避免冗余的watchcomputed,注意依赖清晰性
    • watchEffect可能依赖不明确,推荐用精确watch
    • 避免嵌套watch导致链式更新
  • 使用虚拟滚动、分页,减少大列表通信与渲染压力
    • 使用vue-virtual-scroller等库
    • 父子间只传当前视口数据,减少 props 体积