1、Vue3 中computed和 watch、watchEffect区别
答:computed、watch和watchEffect都是用于监听响应式数据变化的工具,但他们的用途、机制和适应场景各不相同。
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-memo或markRaw避免不必要的响应式追踪(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>缓存部分组件,提升切换性能
- 避免冗余的
watch和computed,注意依赖清晰性- 用
watchEffect可能依赖不明确,推荐用精确watch - 避免嵌套
watch导致链式更新
- 用
- 使用虚拟滚动、分页,减少大列表通信与渲染压力
- 使用
vue-virtual-scroller等库 - 父子间只传当前视口数据,减少 props 体积
- 使用