> 文章列表 > Vue3响应式内部原理

Vue3响应式内部原理

Vue3响应式内部原理

响应式内部原理—Vue的核心机制之一

Vue3响应式内部原理

响应式的本质:当数据变化后会自动执行某个函数映射到组件,自动触发组件的重新渲染。

响应式的实现方式就是劫持数据,Vue3的reactive就是通过Proxy劫持数据,由于劫持的是整个对象,所以可以检测到任何对象的修改,弥补了2.0的不足。

名词解释

副作用函数:函数的执行会直接或间接影响其他函数的执行,这时我们说函数产生了副作用。副作用很容易产生,例如一个函数修改了全局变量,这其实也是一个副作用。

targetmap 是一个 weakmap 类型的集合,用来存储副作用函数,从类型定义可以看出 targetmap的数据结构方式:

  • weakmap 由 target --> map 构成
  • map 由 key --> set 构成

其中 weakmap 的键是原始对象 target,weakmap 的值是一个 map 实例,map 的键是原始对象 target 的 key,map 的值是一个由副作用函数组成的 set。

effect函数:创建一个副作用函数,接受两个参数,分别是用户自定义的fn函数和options 选项。

track收集依赖访问数据的时候,触发get函数,get函数最核心的部分是执行track函数收集依赖。这其实是一种懒操作。

trigger派发更新:当对属性进行赋值时,会触发代理对象的 set 拦截函数执行。

track函数收集依赖的实现


export function track(target: object, type: trackoptypes, key: unknown) {// 如果开启了依赖收集并且有正在执行的副作用,则收集依赖if (shouldtrack && activeeffect) {// 在 targetmap 中获取对应的 target 的依赖集合let depsmap = targetmap.get(target)if (!depsmap) {// 如果 target 不在 targetmap 中,则将当前 target 添加进 targetmap 中,并将 targetmap 的 value 初始化为 new map()。targetmap.set(target, (depsmap = new map()))}// 从依赖集合中获取对应的 key 的依赖let dep = depsmap.get(key)if (!dep) {// 如果 key 不存在,将这个 key 作为依赖收集起来,并将依赖初始化为 new set()depsmap.set(key, (dep = createdep()))}// 最后调用 trackeffects收集副作用函数,将副作用函数收集到依赖集合depsmap中。const eventinfo = __dev__? { effect: activeeffect, target, type, key }: undefinedtrackeffects(dep, eventinfo)}
}

trackeffects 函数

// 收集副作用函数,在 trackeffects 函数中,检查当前正在执行的副作用函数 activeeffect 是否已经被收集到依赖集合中,如果没有,就将当前的副作用函数收集到依赖集合中。同时在当前副作用函数的 deps 属性中记录该依赖。
export function trackeffects(dep: dep,debuggereventextrainfo?: debuggereventextrainfo
) {let shouldtrack = falseif (effecttrackdepth <= maxmarkerbits) {if (!newtracked(dep)) {dep.n |= trackopbit // set newly trackedshouldtrack = !wastracked(dep)}} else {// full cleanup mode.// 如果依赖中并不存当前的 effect 副作用函数shouldtrack = !dep.has(activeeffect!)}if (shouldtrack) {// 将当前的副作用函数收集进依赖中dep.add(activeeffect!)// 并在当前副作用函数的 deps 属性中记录该依赖activeeffect!.deps.push(dep)if (__dev__ && activeeffect!.ontrack) {activeeffect!.ontrack(object.assign({effect: activeeffect!},debuggereventextrainfo))}}
}

trigger 派发更新

对属性进行赋值时,会触发代理对象的 set 拦截函数执行,如下面的代码所示:

const obj = { foo: 1 } 
//通过代理对象p 访问 foo 属性,便会触发 set 拦截函数的执行
const p = new proxy(obj, {// 拦截设置操作set(target, key, newval, receiver){// 如果属性不存在,则说明是在添加新属性,否则设置已有属性const type = object.prototype.hasownproperty.call(target,key) ?  'set' : 'add'    // 设置属性值const res = reflect.set(target, key, newval, receiver)// 把副作用函数从桶里取出并执行,将 type 作为第三个参数传递给 trigger 函数trigger(target,key,type)   return res}// 省略其他拦截函数
})p.foo = 2

trigger 函数

export function trigger(target: object,type: triggeroptypes,key?: unknown,newvalue?: unknown,oldvalue?: unknown,oldtarget?: map<unknown, unknown> | set<unknown>
) {
//首先检查当前 target 是否有被追踪,如果从未被追踪过,即target的依赖未被收集,则不需要执行派发更新,直接返回即可。const depsmap = targetmap.get(target)// 该 target 从未被追踪,则不继续执行if (!depsmap) {// never been trackedreturn}// 接着创建一个 set 类型的 deps 集合,用来存储当前target的这个 key 所有需要执行派发更新的副作用函数。let deps: (dep | undefined)[] = []//接下来就根据操作类型type 和 key 来收集需要执行派发更新的副作用函数。//如果操作类型是 triggeroptypes.clear ,那么表示需要清除所有依赖,将当前target的所有副作用函数添加到 deps 集合中。if (type === triggeroptypes.clear) {// collection being cleared// trigger all effects for target// 当需要清除依赖时,将当前 target 的依赖全部传入deps = [...depsmap.values()]} else if (key === 'length' && isarray(target)) {// 处理数组的特殊情况depsmap.foreach((dep, key) => {// 如果对应的长度, 有依赖收集需要更新if (key === 'length' || key >= (newvalue as number)) {deps.push(dep)}})} else {// schedule runs for set | add | delete// 在 set | add | delete 的情况,添加当前 key 的依赖if (key !== void 0) {deps.push(depsmap.get(key))}// also run for iteration key on add | delete | map.setswitch (type) {case triggeroptypes.add:if (!isarray(target)) {deps.push(depsmap.get(iterate_key))if (ismap(target)) {// 操作类型为 add 时触发map 数据结构的 keys 方法的副作用函数重新执行deps.push(depsmap.get(map_key_iterate_key))}} else if (isintegerkey(key)) {// new index added to array -> length changesdeps.push(depsmap.get('length'))}breakcase triggeroptypes.delete:if (!isarray(target)) {deps.push(depsmap.get(iterate_key))if (ismap(target)) {// 操作类型为 delete 时触发map 数据结构的 keys 方法的副作用函数重新执行deps.push(depsmap.get(map_key_iterate_key))}}breakcase triggeroptypes.set:if (ismap(target)) {deps.push(depsmap.get(iterate_key))}break}}const eventinfo = __dev__? { target, type, key, newvalue, oldvalue, oldtarget }: undefinedif (deps.length === 1) {if (deps[0]) {if (__dev__) {triggereffects(deps[0], eventinfo)} else {triggereffects(deps[0])}}} else {const effects: reactiveeffect[] = []// 将需要执行的副作用函数收集到 effects 数组中for (const dep of deps) {if (dep) {effects.push(...dep)}}if (__dev__) {triggereffects(createdep(effects), eventinfo)} else {triggereffects(createdep(effects))}}
}

triggereffects 函数

//triggereffects 函数中,遍历需要执行的副作用函数集合,如果当前副作用函数存在调度器,则执行该调度器,否则直接执行该副作用函数的 run 方法,执行更新。
export function triggereffects(dep: dep | reactiveeffect[],debuggereventextrainfo?: debuggereventextrainfo
) {// spread into array for stabilization// 遍历需要执行的副作用函数集合   for (const effect of isarray(dep) ? dep : [...dep]) {// 如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行if (effect !== activeeffect || effect.allowrecurse) {if (__dev__ && effect.ontrigger) {effect.ontrigger(extend({ effect }, debuggereventextrainfo))}if (effect.scheduler) {// 如果一个副作用函数存在调度器,则调用该调度器effect.scheduler()} else {// 否则直接执行副作用函数effect.run()}}}
}