ref 和 reactive
reactive
的实现:reactive 通过 Proxy 对对象的每个属性进行深度代理,实现响应式。这种设计使得 reactive 能自动追踪所有嵌套属性的变化,但由于 Proxy 无法直接处理基本数据类型(如 number 、 string 、 boolean ),因此, reactive 不适用于基本数据类型。ref
的实现:为了实现基本数据类型的响应式,Vue 设计了 ref。ref 会将基本数据类型封装为一个包含 value 属性的对象,通过 getter 和 setter 实现响应式依赖追踪和更新。当访问或修改 ref.value 时,Vue 内部会触发依赖更新。此外,对于复杂数据类型(如对象或数组),ref 的内部实现会直接调用 reactive,将复杂数据类型变为响应式。
建议使用 ref
vue3 和 vue2 的区别
- 增加组合式 API(Composition API)
- 基于 Proxy 实现响应式
- 完全用 TypeScript 重写
- 多个根节点,组件模板不再强制要求单个根元素
- 支持多个 v-model 绑定
- 生命周期钩子变化
$on
,$off
,$once
实例方法被移除- 移除过滤器 (filters),推荐使用方法或计算属性代替
- 编译优化:更高效的代码分割和 Tree Shaking 技术,减少打包体积
Composition API 和 Options API 相比,核心优势是什么,主要解决了过去开发中的哪些痛点
- 逻辑复用过去只能用 Mixin,同名覆盖、来源不清晰 ⇒ 现在自定义 Hook 封装
- 以前同一个功能分散在 data、method、watch 等等地方,查看和维护很痛苦 ⇒ 现在相同功能放在一起,更内聚
- 以前各种 this 写法 ⇒ 现在函数式编程避免 this 上下文混乱
Vue3 diff 算法做了哪些优化?
- Patch Flag:在编译模板时,标记节点的哪些部分可能会发生变化,运行时根据标记跳过无关属性的比较
- 静态提升:将纯静态节点提升为常量,静态内容完全跳过 Diff 过程,后续更新时直接复用
- 最长递增子序列算法:Vue3 利用最长递增子序列(LIS)算法来优化对比流程。通过找到新旧节点之间的最长递增子序列,Vue3 可以减少移动操作,从而提高更新效率。
- 事件缓存:自动缓存内联事件处理器,避免每次渲染重新创建新函数
watch 和 watchEffect 的区别
依赖追踪方式
- watch :需要显式声明依赖,监听指定的数据源;可以监听多个数据源或进行深度监听。
- watchEffect :会自动追踪 作用域内所有的响应式依赖,不需要显式声明依赖。
执行时机
- watch :在监听的响应式数据变化后执行。
- watchEffect :在 组件挂载时 立即执行一次副作用,并在 依赖发生变化时 再次执行。
响应式原理
什么是响应式?
当数据变化时,自动监听到数据的变更并更新所有使用到该数据地方的值
响应式实现?
数据劫持
在 JavaScript 中有两种劫持 property 访问的方式:getter/setter
和 Proxy
Vue2 用的是 getter/setter
,Vue3 用的是 Proxy
为什么要用 Proxy?
因为 Vue2 的方式有几个问题:Vue2 响应式缺点
响应式系统
上图就是响应式系统 effect
的核心流程:
effect 是一个自动追踪依赖的副作用函数:它立即执行这段函数,并在函数里读取到的任何响应式数据变化时再次自动执行。(组件渲染、计算属性、侦听器,内部都用 effect 实现)
computed 原理
接收一个 getter(数据源) 作为参数,内部调用 effect 并传入配置项(lazy)创建一个 effect,并通过 effect 的返回值拿到计算属性的计算结果,并返回
function computed(getter) {
// 把 getter 作为副作用函数,创建一个 lazy 的 effect(不立即执行 effect)
const effectFn = effect(getter, {lazy: true})
const obj = {
// 当读取 value 时才执行 effectFn
get value() {
return effectFn()
}
}
return obj
}
缓存功能实现:
添加 value 和 dirty 标志,以及调度器(调度器传入后,effect 函数就会优先去执行传入的 scheduler 调度器函数)
function computed(getter) {
// value 用来缓存上一次计算的值
let value
// dirty 标志,用来标识是否需要重新计算值,为 true 则意味着“脏”,需要计算
let dirty = true
// 把 getter 作为副作用函数,创建一个 lazy 的 effect(不立即执行 effect)
const effectFn = effect(getter, {
lazy: true,
// 添加调度器,在调度器中将 dirty 重置为 true
scheduler() {
dirty = true
}
})
const obj = {
// 当读取 value 时才执行 effectFn
get value() {
if (dirty) {
value = effectFn()
dirty = false
}
return value
}
}
return obj
}
总结
计算属性就是内部调用了 effect
函数并传入配置让它不立即执行,同时返回一个 get 对象在访问值的时候去真正执行 effect。
缓存功能的实现是利用一个内部的 value 变量和一个 dirty 标志,只有数据为脏的时候才会去重新调用 effect 函数进行计算。
watch 原理
也是对 effect
的封装,监听的数据源传入 effect 函数,并传入调度器作为 cb 的实现
function watch(source, cb) {
// 兼容数据源的不同形式
let getter
if (typeof source === 'function') {
getter = source
} else {
getter = () => traverse(source) // 调用 traverse 递归地读取
}
// 定义旧值与新值
let oldValue, newValue
// 使用 effect 注册副作用函数时,开启 lazy 选项,并把返回值存储到 effectFn 中以便后续手动调用
const effectFn = effect(
() => getter(),
{
lazy: true,
scheduler() {
// 在 scheduler 中重新执行副作用函数,得到的是新值
newValue = effectFn()
// 将旧值和新值作为回调函数的参数
cb(newValue, oldValue)
// 更新旧值,不然下一次会得到错误的旧值
oldValue = newValue
}
}
)
oldValue = effectFn() // 手动调用 effectFn 函数得到的返回值就是旧值,即第一次执行得到的值
}
立即执行原理?
就是增加一个 immediate 配置项,为 true 时直接执行 scheduler 的调度任务,否则只执行 oldValue = effectFn()
执行时机原理?
当 flush 的值为 ‘post’ 时,调度函数就将副作用函数放到一个微任务队列中
const job = () => {
newValue = effectFn()
cb(newValue, oldValue)
oldValue = newValue
}
scheduler() {
if (options.flush === 'post') {
const p = Promise.resolve()
p.then(job)
} else {
job()
}
}
watchEffect 原理
和 watch 一样,内部都是调用同一个处理函数(doWatch),只是不传入第二个 cb 参数,doWatch 内部发现没有 cb 参数就走 watchEffect 逻辑
ref 原理
为什么引入 ref?
非原始值的响应式方案是用 reactive
,但是 Proxy 只能代理对象,不能代理基本数据类型,所以引入 ref 的概念
实现原理
将基本类型包装成对象,通过 .value
访问,然后在对象的 get 里进行 track()
,在 set 里进行 trigger()
。如果传入的是对象,则调用 reactive
进行处理
Pinia 和 Vuex
区别:
- Pinia 去掉了 mutation
- Pinia 设计更简洁,支持 Composition API 风格
- Pinia 可以多个 Store 实例,Vuex 单一实例(modules 嵌套)
- Pinia 体积更小
为什么 Vuex 必须有 Mutation?
因为 Vue 2 基于 Object.defineProperty
实现响应式,无法检测对象属性的直接添加/删除,直接修改状态(如 state.count++
)不能被 DevTools 追踪
为了实现 DevTools 时间旅行功能、能精确知道谁变了、何时变,Vuex 强制所有修改走 Mutation → 统一记录日志
为什么 Pinia 可以去掉 mutation?
Vue3 基于 Proxy 实现响应式,能精确知道数据的任何变更情况