「这是我参与2022首次更文挑战的第19天,活动详情查看:2022首次更文挑战」。
前言
本文对Vue3.0的响应式源码进行学习,Vue官方文档: 深入响应性原理
Vue 最独特的特性之一,是其非侵入性的响应性系统。数据模型是被代理的 JavaScript 对象。而当你修改它们时,视图会进行更新。
响应式API体验
Vue3 在 setup 中如果想使用响应式的数据,可以使用 reactive 和 Refs 这两个API
<body>
<div id="app">
<h1>vue3 reactivity API</h1>
<p>{{ state.counter }}</p>
<p>{{ counter2 }}</p>
</div>
<script>
const app = Vue.createApp({
setup() {
const state = Vue.reactive({
counter: 1
})
setInterval(() => {
state.counter++
}, 1000)
Vue.watch(() => state.counter, () => {
})
const counter2 = Vue.ref(1)
setInterval(() => {
counter2.value++
}, 1000)
Vue.watch(counter2, () => {
})
return {
state,
counter2
}
}
})
app.mount('#app')
</script>
</body>
使用上的区别
-
ref()
的作用就是将一个 原始数据类型(primitive data type) 转换成一个带有响应式特性,reactive()
是赋予 对象(Object) 响应式的特性 -
使用或修改 ref 数据,需要带上
.value
,使用或修改 rective 的数据直接使用即可 -
导出以后在 html 中使用直接使用 ref 则不需要
.value
,使用 rective 中包裹的对象数据需要带上外层 key 值,比如state.xxx
-
rective 中的数据可以通过
toRef
,toRefs
转换成 ref 导出在 html 直接使用
// 转换单个属性
const state = reactive({
foo: 1,
bar: 2
})
const fooRef = toRef(state, 'foo')
fooRef.value++
console.log(state.foo) // 2
state.foo++
console.log(fooRef.value) // 3
// 转换整个对象
const state = reactive({
foo: 1,
bar: 2
})
const stateAsRefs = toRefs(state)
// ref 和原始 property 已经“链接”起来了
state.foo++
console.log(stateAsRefs.foo.value) // 2
stateAsRefs.foo.value++
console.log(state.foo) // 3
reactive函数源码解析
-
找到
reactive()
定义的位置packages\reactivity\src\reactive.ts
-
reactive()
会返回一个createReactiveObject()
执行后的结果,作用就是可以将普通的对象转为响应式对象
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (isReadonly(target)) {
return target
}
// 如何将传入普通对象转换为响应式对象
return createReactiveObject(
target,
false,
mutableHandlers, /* 普通对象的代理处理器 */
mutableCollectionHandlers,
reactiveMap
)
}
createReactiveObject()
中对第一个参数target
(即reactive
包裹的对象)做了一层Proxy
代理 MDN Proxy 文档- 一般传入的都是普通对象,因此在
Proxy
中的 handler 会使用baseHandlers
是在reactive()
中传入的mutableHandlers
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
...
// 创建代理对象,将传入的原始对象作为代理目标
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}
mutableHandlers
位置packages\reactivity\src\baseHandlers.ts
,其中对getter
和setter
做了拦截操作
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
- getter 时分别对代理对象类型做了判断并且做了对应的操作,这里说一下如果是对象并且不是只读的情况下,会执行到
track()
去收集依赖
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
...
// target是数组的处理
const targetIsArray = isArray(target)
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
// target是对象
const res = Reflect.get(target, key, receiver)
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res
}
// 如果不是只读
if (!isReadonly) {
// 建立target,key和依赖函数之间的关系
track(target, TrackOpTypes.GET, key)
}
if (shallow) {
return res
}
if (isRef(res)) {
// ref unwrapping - does not apply for Array + integer key.
const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
return shouldUnwrap ? res.value : res
}
// 如果是对象递归处理
if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
- track 位置
packages\reactivity\src\effect.ts
,会将收集到的依赖放到depsMap
对象中,然后跟踪依赖trackEffects
,在将来某个时候触发更新
// 依赖收集
export function track(target: object, type: TrackOpTypes, key: unknown) {
...
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = createDep()))
}
...
// 依赖跟踪
trackEffects(dep, eventInfo)
}
- 收集完了依赖,可以在单步调试中观察数据修改后,是怎样触发更新的,具体将在下一篇中进行讲解