系列文章目录
- 目录分析
- 初始化流程
- 响应式系统
- shared工具函数
文章目录
- 系列文章目录
- 前言
- 一、定义响应式数据
- 1、reactive(target)
- 2、createReactiveObject
- 2.1 入参
- 2.2 响应式创建过程
- 2.3 proxy handler
- 2.4 get 做了什么?
- 2.5 set 做了什么?
- 二、依赖收集与派发更新
- 1、依赖收集——track
- 2、activeEffect
- 3、effect
- 3.1 effect如何被定义
- 3.2 effect都在什么时候创建呢?
- 3、依赖更新派发——trigger
- 三、reactivity模块其他API
- 总结
前言
无论在vue2或者vue3中,实现一个vue的响应式系统(改变数据,自动渲染组件),大致分为两部分:
packages/reactivity
文件夹下的src中就是实现响应式逻辑的源码
一、定义响应式数据
- vue2中,只需要在组件的
data
中定义数据,这些数据自动就会变成响应式的数据。原理是在组件初始化时,遍历data中的属性,依次利用Object.defineProperty
中的getter setter进行拦截操作。 - 而从vue3的setup API可知,我们需要调用 reactive 方法,手动定义一个响应式数据。
如以下代码示例:
<template>
<p>{{obj.count}}</p>
</template>
import {reactive} from 'vue'
const obj = reactive({count: 0})
obj.count++
那 reactive
方法背后的实现原理是什么呢???下面通过分析源码看下~~
1、reactive(target)
reactive方法主要作用:接收一个普通对象target然后返回该普通对象的响应式代理
定位到[runtime-core/reactivity/reactive.ts]源码:
export function reactive(target: object) {
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
reactive
作为定义响应式数据的入口API
,将一个target普通对象包装成响应式的对象,里面核心实现方法是:createReactiveObject
(如下)
2、createReactiveObject
方法主要作用:创建一个响应式数据对象
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
if (!isObject(target)) {
return target
}
if (target[ReactiveFlags.RAW] &&!(isReadonly && target[ReactiveFlags.IS_REACTIVE])) {
return target
}
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}
2.1 入参
target
传入的要被创建响应式的对象,同reactive(见上)的targetisReadonly
是否只读 ,通过reactive创建的为false
非只读(见上)baseHandlers
普通对象(Object,Array)的proxy handler(get,set,deleteProperty,has, ownKeys)。通过reactive创建的为mutableHandlers
(见[baseHandlers.ts]),为之后new Proxy(target, handlers)
传入
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
-
collectionHandlers
!!!集合数据(Map,WeakMap,Set,WeakSet)的proxy handler 。通过reactive创建的为mutableCollectionHandlers
(见[collectionHandlers.ts]),为之后new Proxy(target, handlers)
传入
-
proxyMap
存储所有的proxy的WeakMap(Map与WeakMap区别可自行百度)结构数据,key是 target对象, value是响应式对象。reactive
方法传入的是reactiveMap
,
export const reactiveMap = new WeakMap<Target, any>()
2.2 响应式创建过程
首先,判断入参target是否为有效参数
isObject
判断target是否为为对象,若不是对象,直接返回target- 若target已经是proxy对象,直接返回target
existingProxy = proxyMap.get(target)
若从全局proxyMap对象中能拿到该对象的代理对象,则直接返回拿到的代理对象- 检查target的类型,若不是object array map set weakmap weakset 中的,则直接返回target
接着,创建proxy对象
- 通过
new Proxy(target, handlers)
为target创建代理 - 并且根据target类型,选择合适的
handers:TargetType.COLLECTION ? collectionHandlers : baseHandlers
。 Map
,Set
,WeakMap
,WeakSet
集合(TargetType.COLLECTION
)类型,在proxy中使用的是collectionHandlers
Object
,Array
在proxy中使用的是 baseHandlers
,下面具体看一下
最后,全局存储target对应的Proxy对象: proxyMap.set(target, proxy)
,并返回:return proxy
2.3 proxy handler
从上可知,reactive方法创建响应式对象时,传入的baseHandlers具体为mutableHandlers
,
mutableHandlers是从packages/src/reactivity/baseHandlers.ts
文件引入而来,baseHandlers.ts
文件定义了所有不同的baseHandlers,对应不同的数据处理
import {
mutableHandlers,
readonlyHandlers,
...
} from './baseHandlers'
以baseHandlers.ts
中的 mutableHandlers
为例:new Proxy(target, mutableHandlers)
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
通过以上分析可知,vue3将一个普通对象变为响应式对象,核心是通过new Proxy(ES6语法)
创建代理,拦截set,get,deleteProperty,has,ownKeys
操作监听。
2.4 get 做了什么?
访问对象属性会触发get函数
const get = createGetter()
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
if (key === ReactiveFlags.RAW && receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap
).get(target)
) {
return target
}
const targetIsArray = isArray(target)
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
const res = Reflect.get(target, key, receiver)
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
return res
}
}
以上可知,get函数大致做了2件事:
track
依赖收集(类似于vue2的getter中的dep.depend()
)。把组件渲染期间 依赖的property记录下来,之后触发依赖项setter
时,会通知更新,使得组件重新渲染- get最终就是通过
Reflect.get
要拿到代理对象属性值res
思考:为什么用 Reflect.get,而不是直接 target[key] 返回呢?
Reflect.get(target, key, receiver)
- target (取值的目标对象)、 key (获取的值的键值)、receiver(如果target对象中指定了getter,receiver则为getter调用时的this值)。
- 返回值:属性的值
var target = {
foo: 1,
get baz() {
return this.foo;
},
};
const observed = reactive(target)
此时,如果用target[key]
取值,那么 this.foo 中的 this 就指向的是 target,而不是 observed,此时 this.foo 就不能收集到 foo 的依赖了,如果 observed.foo = 20 改变了 foo 的值,那么是无法触发依赖回调的,所以需要利用 Reflect.get 中的第三个参数receiver
将 getter 里的 this 指向代理对象observed。
2.5 set 做了什么?
设置对象属性会触发set函数
const set = createSetter()
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
let oldValue = (target as any)[key]
if (!shallow) {
value = toRaw(value)
oldValue = toRaw(oldValue)
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
}
}
const result = Reflect.set(target, key, value, receiver)
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}
以上可知,set函数大致做了2件事:
- set就是通过
Reflect.set
要设置代理对象属性值 trigger
派发更新,使得组件重新渲染。
二、依赖收集与派发更新
1、依赖收集——track
track方法在 reactivity模块中effect.ts
模块有定义,如下:
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (!shouldTrack || activeEffect === undefined) {
return
}
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect)
activeEffect.deps.push(dep)
}
}
- 以上涉及到了2个关键对象:
activeEffect
、targetMap
。最终将activeEffect收集到targetMap全局Map结构中,同时将dep放到activeEffect下的deps数组。 - 在vue2中,做依赖收集时,收集的是
watcher
,而vue3已经没有了watcher概念,取而代之的是effect
(副作用函数)。 targetMap
是一个全局WeakMap对象,作为一个依赖收集容器,用于存储target[key]相应的dep依赖。targetMap.get(target)
获取target对应的depsMap
,depsMap内部又是一个Map,key为target中的属性,depsMap.get(key)
则为Set结构存储的target[key]对应的dep
,dep中则是存储了所有依赖的effects。- 方便在修改数据触发set时,找到对应的effect依赖并执行。
type Dep = Set<ReactiveEffect>
type KeyToDepMap = Map<any, Dep>
const targetMap = new WeakMap<any, KeyToDepMap>()
2、activeEffect
activeEffect同样在 reactivity模块中effect.ts
模块有定义,表示正在运行中的effect
:
let activeEffect: ReactiveEffect | undefined
3、effect
effect 作为 reactive 的核心,主要负责收集依赖,更新依赖。
3.1 effect如何被定义
看下源码方法定义:
export function effect<T = any>(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
if (isEffect(fn)) {
fn = fn.raw
}
const effect = createReactiveEffect(fn, options)
if (!options.lazy) {
effect()
}
return effect
}
isEffect(fn)
判定fn是否已经是一个effect,若是fn.raw就是原始的回调函数,fn = fn.raw (effect.raw = fn)createReactiveEffect(fn, options)
返回一个effect函数options.lazy
若为false,非惰性更新,则立即执行一次。computed effect为true
下面看看effect如何被创建的:
function createReactiveEffect<T = any>(
fn: () => T,
options: ReactiveEffectOptions
): ReactiveEffect<T> {
const effect = function reactiveEffect(): unknown {
if (!effect.active) {
return options.scheduler ? undefined : fn()
}
if (!effectStack.includes(effect)) {
cleanup(effect)
try {
enableTracking()
effectStack.push(effect)
activeEffect = effect
return fn()
} finally {
effectStack.pop()
resetTracking()
activeEffect = effectStack[effectStack.length - 1]
}
}
} as ReactiveEffect
effect.id = uid++
effect.allowRecurse = !!options.allowRecurse
effect._isEffect = true
effect.active = true
effect.raw = fn
effect.deps = []
effect.options = options
return effect
}
- 生成一个挂载了一系列属性:
id,allowRecurse,_isEffect,active,raw,deps, options
的reactiveEffect方法。 - effectStack是一个作为全局变量的effect栈,类似于vue2中存储watcher的栈:
const effectStack: ReactiveEffect[] = []
- 在执行effect()时,若effectStack没有当前effect,首先
清空effect.deps数组中的依赖
,每次effect 运行时都会重新收集依赖。(在上面track那里,曾给deps数组塞值:activeEffect.deps.push(dep)) effectStack.push(effect)
,将当前effect压入effectStack栈activeEffect = effect
, 给activeEffect赋值,表示当前正在运行的effect,activeEffect 主要为了在收集依赖的时候使用- 执行回调
fn()
,最后将effect出栈,恢复之前的状态,重置activeEffect
3.2 effect都在什么时候创建呢?
1、mountComponent
2、computed
3、watcher、watchEffect
3、依赖更新派发——trigger
依赖收集完毕,接下来target的属性值修改会触发trigger,拿到相应的依赖并执行effect
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
const depsMap = targetMap.get(target)
if (!depsMap) {
return
}
const effects = new Set<ReactiveEffect>()
const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect || effect.allowRecurse) {
effects.add(effect)
}
})
}
}
if (type === TriggerOpTypes.CLEAR) {
depsMap.forEach(add)
} else if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= (newValue as number)) {
add(dep)
}
})
} else {
if (key !== void 0) {
add(depsMap.get(key))
}
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) {
add(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET:
if (isMap(target)) {
add(depsMap.get(ITERATE_KEY))
}
break
}
}
const run = (effect: ReactiveEffect) => {
...
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else {
effect()
}
}
effects.forEach(run)
}
- 首先校验一下target有没有被收集依赖,若没有收集依赖,则return
- 根据不同的操作clear add delete set,将合规的effect加入到effects set集合中
- 遍历effects set集合,执行effect函数
以上,整个响应式系统已经形成了一个基本的闭环。
三、reactivity模块其他API
上一篇大致也介绍了vue源码各目录存放的API,那响应式模块都在runtime-core/reactive目录中。
打开packages/reactivity/src/index.ts
,可看到导出的API
主要是四个部分:
ref
响应式的关键入口,作用同reactivereactive
响应式的关键入口 (本文重点讲述)computed
同vue2 的computed选项effect
作用同vue2的watcher(Vue3中已经没有了watcher概念,由effect取而代之)
export {
ref,
shallowRef,
isRef,
toRef,
toRefs,
unref,
proxyRefs,
customRef,
triggerRef,
Ref,
ToRefs,
UnwrapRef,
ShallowUnwrapRef,
RefUnwrapBailTypes
} from './ref'
export {
reactive,
readonly,
isReactive,
isReadonly,
isProxy,
shallowReactive,
shallowReadonly,
markRaw,
toRaw,
ReactiveFlags,
DeepReadonly,
UnwrapNestedRefs
} from './reactive'
export {
computed,
ComputedRef,
WritableComputedRef,
WritableComputedOptions,
ComputedGetter,
ComputedSetter
} from './computed'
export {
effect,
stop,
trigger,
track,
enableTracking,
pauseTracking,
resetTracking,
ITERATE_KEY,
ReactiveEffect,
ReactiveEffectOptions,
DebuggerEvent
} from './effect'
export { TrackOpTypes, TriggerOpTypes } from './operations'
总结
vue2响应式原理:
- 在 Vue2 里内部通过 Object.defineProperty API 劫持数据的变化,深度遍历 data 函数里的对象,给对象里每一个属性设置 getter、setter。
- 触发 getter 会通过 Dep 类做依赖收集操作,收集当前 Dep.target, 也就是 watcher。
- 触发 setter,执行 dep.notify 通知收集到的各类 watcher 更新,如 computed watcher、user watcher 、渲染 watcher。
Vue3响应式原理:
- Vue3 用 ES6的Proxy 重构了响应式,new Proxy(target, handler)
- Proxy 的 get handle 里 执行track() 用来跟踪收集依赖(收集 activeEffect,也就是 effect )
- Proxy 的 set handle 里执行 trigger() 用来触发响应(执行收集的 effect)
- effect 副作用函数 代替了 watcher
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)