Vue 3.x
源码学习进度
响应式原理 reactivity
reactive
: 为对象、数组、Set
/WeakSet
、Map
/WeakMap
等做响应式转换,而不会对 Date、RegExp、Function 等做响应式处理- 源码位置:
packages/reactivity/src/reactive.ts
- 其他方法
isReactive()
isReadonly()
isProxy()
toRaw()
:递归地获取代理对象observed
的原始对象markRaw(value)
:标记value
为跳转响应式转换,以后即使调用reactive(value)
等操作返回的还是value
本身- baseHandlers.ts: 对象/数组做代理时使用的处理器对象
- 源码位置:
ref(val)
: 将val
转换为 Ref 对象,val
可以是对象或代理对象- 源码位置:
packages/reactivity/src/ref.ts
shallowRef()
isRef()
unref()
: 获取 Ref 对象的原始值toRef(obj, key)
: 将对象的单个key
转为 ObjectRef 对象(类似于 Ref 对象),解决因提前触发值的get
方法(比如解构赋值、扩展运算符)导致的响应式丢失问题toRefs()
: 遍历对象的每一个key
调用toRef
triggerRef()
: 手动触发依赖ref
的effect
重新运行customRef
: 自定义ref
的get
/set
函数- 【TODO】
proxyRefs
- 源码位置:
- 【done】effect.ts
effect()
track()
trigger()
computed
方法- 源码位置:
packages/reactivity/src/computed.ts
computed
具有双重角色,自身既作为ReactiveEffect
依赖外部的依赖项,也作为ComputedRef
(类似于ref
,专用于计算属性)成为外部ReactiveEffect
的依赖项computed
的计算是惰性的,即:- 首次声明
computed()
时不会对传入的getter
进行计算 - 若只声明了
computed
但没有获取computed
的值,当computed
的依赖项改变时,computed
不会进行计算新值,只会标记当前的计算值为dirty
- 首次声明
- 源码位置:
以上这些内容位于vue-next/packages/reactivity
目录下,会单独发包为@vue/reactivity
,可独立于 Vue 3 使用。
响应式的计算和侦听
watchEffect
/watch
方法- 源码位置:
packages/runtime-core/src/apiWatch.ts
watchEffect
方法- 第一个参数只能是函数
- 立即执行传入的函数
fn
,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数fn
watch
- 第一个参数可以是:
getter
函数ref
对象- 响应式对象
- 以上这些组成的数组
- 与
watchEffect
比较,watch
允许我们:- 懒执行副作用;
- 更具体地说明什么状态应该触发侦听器重新运行;
- 访问侦听状态变化前后的值。
- 第一个参数可以是:
- 源码位置:
以上这些内容是 Vue 3 基于@vue/reactivity
的封装,只在 Vue 3 内部使用。
runtime-core
watchEffect/watch
watchEffect
/watch
- 首次都会运行一次
- 调用
watchEffect()
的返回值stop
,可以停止副作用,且将该watcher
从当前 Vue 实例instance.effects
数组里移除
watchEffect(effect, options?)
watch(source, callback, options?)
- 当
source
的依赖项改变导致source
重新计算后,则满足以下条件之一,回调函数callback
会运行- 第一个参数是
ref
,或是reactive
的对象,或是指定了deep: true
- 通过第一个参数获取的值改变了
- 第一个参数是
- 当
为什么解构赋值、扩展运算符等会造成响应式丢失
const obj = reactive({ foo: 1 }) // obj 是响应式数据
const obj2 = { foo: obj.foo }
effect(() => {
console.log(obj2.foo) // 这里读取 obj2.foo
})
obj.foo = 2 // 设置 obj.foo 显然无效
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
第2
行获取obj.foo
时会触发obj.foo
的get
函数进行依赖收集,但是此时activeEffect
为空,这导致obj.foo
没有被任何的effect
所依赖,且赋值结束后,obj2
的值为{ foo: 1 }
,obj.foo
的值是原始值,而不是响应式的,因此在effect()
函数执行时不会将obj.foo
收集为依赖项,因此最后一行给obj.foo
赋值时不会触发effect()
的重新计算。
解构赋值、扩展运算符也是同理,都是先将值获取到再赋值给变量,而这个过程中activeEffect
都为空,不会进行依赖收集,而赋值后的变量不是响应式的。