Vue3.0 响应式系统源码逐行分析讲解


Posted in Javascript onOctober 14, 2019

前言

关于响应式原理想必大家都很清楚了,下面我将会根据响应式API来具体讲解Vue3.0中的实现原理, 另外我只会针对get,set进行深入分析,本文包含以下API实现,推荐大家顺序阅读

  • effect
  • reactive
  • readonly
  • computed
  • ref

对了,大家一定要先知道怎么用哦~

引子

先来段代码,大家可以直接复制哦,注意引用的文件

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
 <title>Document</title>
 <script src="../packages/vue/dist/vue.global.js"></script>
</head>
<body>
 <div id="app"></div>
 <script>
  const { reactive, computed, effect, watch, createApp } = Vue
  const App = {
   template: `
    <div id="box">
      <button @click="increment">{{ state.count }}</button>
    </div> 
   `,
   setup() {
    const state = reactive({
     count: 0
    })
    function increment(e) {
     state.count++
    }
    effect(() => {
     console.log('count改变', state.count);
    })
    return {
     state,
     increment
    }
   }
  }
  createApp().mount(App, '#app')
 </script>
</body>
</html>

这段代码,想必大家都看得懂,点击后count增加,视图也随之更新,effect监听了count改变,那么为什么effect能观察到count变化呢,还有为什么reactive可以实现响应式?

effect

为什么要先说这个函数呢,因为它和其他函数都息息相关,只有先了解它才能更好的理解其他响应式API

上源码

export function effect(
 fn: Function,
 options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect {
 if ((fn as ReactiveEffect).isEffect) {
  fn = (fn as ReactiveEffect).raw
 }
 const effect = createReactiveEffect(fn, options)
 if (!options.lazy) {
  effect()
 }
 return effect
}

if判断,判断如果传入的fn函数,它已经是effect了,也就是一个标识,直接获取该函数上的raw属性,这个属性后面会讲到

调用createReactiveEffect

如果options中有lazy,就会立即调用effect,其实本质上调用的还是传入的fn函数

// 了解一下options有哪些
{
 lazy?: boolean // 是否立即调用fn
 computed?: boolean // 是否是computed
 scheduler?: (run: Function) => void // 在调用fn之前执行
 onTrack?: (event: DebuggerEvent) => void // 在依赖收集完成之后调用
 onTrigger?: (event: DebuggerEvent) => void // 在调用fn之前执行,源码上来看和scheduler调用时机一样,只是传入参数不同
 onStop?: () => void // 清除依赖完成后调用
}

返回effect

createReactiveEffect

上面提到了createReactiveEffect函数,我们来看看它的实现

function createReactiveEffect(
 fn: Function,
 options: ReactiveEffectOptions
): ReactiveEffect {
 // 又包装了一层函数
 const effect = function effect(...args): any {
  return run(effect as ReactiveEffect, fn, args)
 } as ReactiveEffect
 effect.isEffect = true // 标识effect
 effect.active = true // 如果active
 effect.raw = fn // 传入的回调
 effect.scheduler = options.scheduler
 effect.onTrack = options.onTrack
 effect.onTrigger = options.onTrigger
 effect.onStop = options.onStop
 effect.computed = options.computed
 effect.deps = [] // 用于收集依赖
 return effect
}

注意,敲黑板,这里有个run函数,很重要,因为它保存了依赖

function run(effect: ReactiveEffect, fn: Function, args: any[]): any {
 if (!effect.active) {
  return fn(...args)
 }
 if (activeReactiveEffectStack.indexOf(effect) === -1) {
  cleanup(effect)
  try {
   activeReactiveEffectStack.push(effect)
   return fn(...args)
  } finally {
   activeReactiveEffectStack.pop()
  }
 }
}

他把依赖存储在了一个全局的数组中activeReactiveEffectStack, 他以栈的形式存储,调用完依赖后,会弹出,大家要留意一下这里,后面会用到

怎么样,是不是很简单~

reactive

export function reactive(target: object) {
 // 如果target是已经被readonly对象,那么直接返回对应的proxy对象
 if (readonlyToRaw.has(target)) {
  return target
 }

 // 如果target是已经被readonly对象,那么直接返回对应的真实对象
 if (readonlyValues.has(target)) {
  return readonly(target)
 }
 return createReactiveObject(
  target,
  rawToReactive,
  reactiveToRaw,
  mutableHandlers,
  mutableCollectionHandlers
 )
}

前两个if是用来处理这种情况的

// 情况一
const state1 = readonly({ count: 0 })
const state2 = reactive(state1)

// 情况二
const obj = { count: 0 }
const state1 = readonly(obj)
const state2 = reactive(obj)
可以看到reactive它的参数是被readonly的对象,reactive不会对它再次创建响应式,而是通过Map映射,拿到对应的对象,即Proxy <==> Object的相互转换。


createReactiveObject创建响应式对象,注意它的参数
createReactiveObject(
  target,
  rawToReactive,  // Object ==> Proxy
  reactiveToRaw,  // Proxy ==> Object
  mutableHandlers, // get set has ...
  mutableCollectionHandlers // 很少会用,不讲了~
)

以上就是reative一开始所做的一些事情,下面继续分析createReactiveObject

createReactiveObject

function createReactiveObject(
 target: any,
 toProxy: WeakMap<any, any>,
 toRaw: WeakMap<any, any>,
 baseHandlers: ProxyHandler<any>,
 collectionHandlers: ProxyHandler<any>
) {
 // 如果不是对象,在开发环境报出警告
 if (!isObject(target)) {
  if (__DEV__) {
   console.warn(`value cannot be made reactive: ${String(target)}`)
  }
  return target
 }

 let observed = toProxy.get(target)
 // 如果目标对象已经有proxy对象,直接返回
 if (observed !== void 0) {
  return observed
 }

 // 如果目标对象是proxy的对象,并且有对应的真实对象,那么也直接返回
 if (toRaw.has(target)) {
  return target
 }
 // 如果它是vnode或者vue,则不能被观测
 if (!canObserve(target)) {
  return target
 }
 // 判断被观测的对象是否是set,weakSet,map,weakMap,根据情况使用对应proxy的,配置对象
 const handlers = collectionTypes.has(target.constructor)
  ? collectionHandlers
  : baseHandlers
 observed = new Proxy(target, handlers)
 toProxy.set(target, observed)
 toRaw.set(observed, target)
 if (!targetMap.has(target)) {
  targetMap.set(target, new Map())
 }
 return observed
}

第一个if,判断是否是对象,否则报出警告

toProxy拿到观测对象的Proxy对象,如果存在直接返回

// 这种情况
const obj = { count: 0 }
const state1 = reative(obj)
const state2 = reative(obj)

toRaw拿到Proxy对象对应的真实对象,如果存在直接返回

// 这种情况
const obj = { count: 0 }
const state1 = reative(obj)
const state2 = reative(state1)

有些情况无法被观测,则直接返回观测对象本身

const canObserve = (value: any): boolean => {
 return (
  !value._isVue &&
  !value._isVNode &&
  observableValueRE.test(toTypeString(value)) &&
  !nonReactiveValues.has(value)
 )
}

设置handlers,即get,set等属性访问器, 注意:collectionHandlers是用来处理观测对象为Set,Map等情况,很少见,这里就不讲了

const handlers = collectionTypes.has(target.constructor)
  ? collectionHandlers
  : baseHandlers

然后创建了Proxy对象,并把观测对象和Proxy对象,分别做映射

observed = new Proxy(target, handlers)
 toProxy.set(target, observed)
 toRaw.set(observed, target)

然后在targetMap做了target ==> Map的映射,这又是干嘛,注意:targetMap是全局的

export const targetMap: WeakMap<any, KeyToDepMap> = new WeakMap()
 if (!targetMap.has(target)) {
  targetMap.set(target, new Map())
 }

在这里先给大家卖个关子,targetMap非常重要,是用来保存依赖的地方

讲完了reactive,可以回到一开始的引子

依赖收集

说到依赖收集,不得不提到,依赖的创建,那么Vue3.0是在哪里创建了渲染依赖呢,大家可以找到下面这段代码以及文件

// vue-next\packages\runtime-core\src\createRenderer.ts
 function setupRenderEffect(
  instance: ComponentInternalInstance,
  parentSuspense: HostSuspsenseBoundary | null,
  initialVNode: HostVNode,
  container: HostElement,
  anchor: HostNode | null,
  isSVG: boolean
 ) {
  // create reactive effect for rendering
  let mounted = false
  instance.update = effect(function componentEffect() {
 // ...
  }, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
 }

代码特别长,我剪掉了中间部分,大家还记得effect有个选项lazy吗,没错,它默认是false,也就会立即调用传入的componentEffect回调,在它内部调用了patch实现了组件的挂载。

敲黑板,关键来了,还记得effect调用,内部会调用run方法吗

function run(effect: ReactiveEffect, fn: Function, args: any[]): any {
 if (!effect.active) {
  return fn(...args)
 }
 if (activeReactiveEffectStack.indexOf(effect) === -1) {
  cleanup(effect)
  try {
   activeReactiveEffectStack.push(effect)
   return fn(...args)
  } finally {
   activeReactiveEffectStack.pop()
  }
 }
}

这里进行了第一步的依赖收集,保存在全局数组中,为了方便触发get的对象,将依赖收集到自己的deps中
然后就是调用patch,进行组件挂载

if (!mounted) {
  const subTree = (instance.subTree = renderComponentRoot(instance))
  // beforeMount hook
  if (instance.bm !== null) {
    invokeHooks(instance.bm)
  }
  patch(null, subTree, container, anchor, instance, parentSuspense, isSVG)
  initialVNode.el = subTree.el
  // mounted hook
  if (instance.m !== null) {
    queuePostRenderEffect(instance.m, parentSuspense)
  }
  mounted = true
}

至于它内部实现,我就不讲了,不是本文重点,然后我们去编译的地方看看

//vue-next\packages\runtime-core\src\component.ts
function finishComponentSetup(
 instance: ComponentInternalInstance,
 parentSuspense: SuspenseBoundary | null
) {
 const Component = instance.type as ComponentOptions
 if (!instance.render) {
  if (Component.template && !Component.render) {
   if (compile) {
    Component.render = compile(Component.template, {
     onError(err) {}
    })
   } else if (__DEV__) {
    warn(
     `Component provides template but the build of Vue you are running ` +
      `does not support on-the-fly template compilation. Either use the ` +
      `full build or pre-compile the template using Vue CLI.`
    )
   }
  }
  if (__DEV__ && !Component.render) {
   warn(
    `Component is missing render function. Either provide a template or ` +
     `return a render function from setup().`
   )
  }
  instance.render = (Component.render || NOOP) as RenderFunction
 }

 // ...其他
}

上面的代码是编译部分,我们来看看例子中编译后是什么样

(function anonymous(
) {
const _Vue = Vue
const _createVNode = Vue.createVNode

const _hoisted_1 = { id: "box" }

return function render() {
 with (this) {
  const { toString: _toString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
  
  return (_openBlock(), _createBlock("div", _hoisted_1, [
   _createVNode("button", { onClick: increment }, _toString(state.count), 9 /* TEXT, PROPS */, ["onClick"])
  ]))
 }
}
})

可以看到,编译的代码中,有使用到state.count,那么就会触发get访问器,从而收集依赖,至于为什么能直接访问到属性,原因是由于with设置了上下文,下面我们具体分析get

get

// vue-next\packages\reactivity\src\baseHandlers.ts
function createGetter(isReadonly: boolean) {
 return function get(target: any, key: string | symbol, receiver: any) {
  const res = Reflect.get(target, key, receiver)
  if (typeof key === 'symbol' && builtInSymbols.has(key)) {
   return res
  }
  // _isRef
  if (isRef(res)) {
   return res.value
  }
  track(target, OperationTypes.GET, key)
  // 如果该属性对应的值还是对象,就继续递归创建响应式
  return isObject(res)
   ? isReadonly
    ? // need to lazy access readonly and reactive here to avoid
     // circular dependency
     readonly(res)
    : reactive(res)
   : res
 }
}

调用Reflect.get获取属性值

如果key是symbol并且是Symbol的一个属性,就直接返回该值

// 这种情况
const key = Symbol('key')
const state = reative({
  [key]: 'symbol value'
})
state[key]

如果值为Ref返回该值的value,看到这里如果大家有了解过ref api的话就知道了,由于ref它自己实现了自己的get,set,所以不再需要执行后面的逻辑,这个在后面会讲

调用track

递归深度观测,使整个对象都为响应式

下面我会详细讲解

track

在讲它之前,先了解它有哪些参数

target: any, // 目标对象
 type: OperationTypes, // 追踪数据变化类型,这里是get
 key?: string | symbol // 需要获取的key
 export const enum OperationTypes {
   SET = 'set',
   ADD = 'add',
   DELETE = 'delete',
   CLEAR = 'clear',
   GET = 'get',
   HAS = 'has',
   ITERATE = 'iterate' 
 }
export function track(
 target: any,
 type: OperationTypes,
 key?: string | symbol
) {
 if (!shouldTrack) {
  return
 }
 // 获取activeReactiveEffectStack中的依赖
 const effect = activeReactiveEffectStack[activeReactiveEffectStack.length - 1]
 if (effect) {
  if (type === OperationTypes.ITERATE) {
   key = ITERATE_KEY
  }
  // 获取目标对象对应的依赖map
  let depsMap = targetMap.get(target)
  if (depsMap === void 0) {
   targetMap.set(target, (depsMap = new Map()))
  }
  // 获取对应属性的依赖
  let dep = depsMap.get(key as string | symbol)
  // 如果该依赖不存在
  if (!dep) {
   // 设置属性对应依赖
   depsMap.set(key as string | symbol, (dep = new Set()))
  }
  // 如果属性对应依赖set中不存在该依赖
  if (!dep.has(effect)) {
   // 添加到依赖set中
   dep.add(effect)
   effect.deps.push(dep)
   if (__DEV__ && effect.onTrack) {
    // 调用onTrack钩子
    effect.onTrack({
     effect,
     target,
     type,
     key
    })
   }
  }
 }
}

activeReactiveEffectStack我两次提到,从它这里拿到了依赖,注意后面执行完依赖后,会从它里面弹出

如果effect存在

从targetMap中获取对象,对饮的Map,具体的数据结构类似这样

const state = reative({
  count: 0
})
effect(() => {
 console.log(state.count) 
})

// 依赖大致结构(随便写的,不太规范)
{
  target(state):Map {
    count: Set (componentEffect渲染依赖, user自己添加的依赖)
  }
}

如果该对象不存在Map,就初始化一个

如果该Map中属性对应的Set不存在,就初始化一个Set

添加依赖到Set中

添加依赖到effect自身的deps数组中

最后调用onTrack回调

// 调用onTrack钩子
effect.onTrack({
  effect,
  target,
  type,
  key
})

OK,Track实现大体就这样,是不是也很简单,有了这些基础,后面要讲的一些API就很容易理解了

set

当我们点击按钮后,就会触发set属性访问器

function set(
 target: any,
 key: string | symbol,
 value: any,
 receiver: any
): boolean {
 value = toRaw(value)
 const hadKey = hasOwn(target, key)
 const oldValue = target[key]
 // 如果旧的值是ref,而新的值不是ref
 if (isRef(oldValue) && !isRef(value)) {
  // 直接更改原始ref即可
  oldValue.value = value
  return true
 }
 const result = Reflect.set(target, key, value, receiver)
 // don't trigger if target is something up in the prototype chain of original
 if (target === toRaw(receiver)) {
  /* istanbul ignore else */
  if (__DEV__) {
   const extraInfo = { oldValue, newValue: value }
   if (!hadKey) {
    trigger(target, OperationTypes.ADD, key, extraInfo)
   } else if (value !== oldValue) {
    trigger(target, OperationTypes.SET, key, extraInfo)
   }
  } else {
   if (!hadKey) {
    trigger(target, OperationTypes.ADD, key)
   } else if (value !== oldValue) {
    trigger(target, OperationTypes.SET, key)
   }
  }
 }
 return result
}

判断旧值是ref,新值不是ref

// 这种情况
const val = ref(0)
const state = reative({
  count: val
})
state.count = 1
// 其实state.count最终还是ref,还是能通过value访问
state.count.value // 1

调用Reflect.set修改值

开发环境下,拿到新旧值组成的对象,调用trigger,为什么开发环境要这么做呢,其实是为了方便onTrigger能拿到新旧值

trigger(target, OperationTypes.ADD, key, extraInfo)

可以看到第二个参数和track是一样的enum,有两种情况,一种我们设置了新的属性和值,另一种修改了原有属性值,下面我们来看看trigger实现。

trigger

export function trigger(
 target: any,
 type: OperationTypes,
 key?: string | symbol,
 extraInfo?: any
) {
 const depsMap = targetMap.get(target)
 if (depsMap === void 0) {
  // never been tracked
  return
 }
 // effect set
 const effects: Set<ReactiveEffect> = new Set()
 // computed effect set
 const computedRunners: Set<ReactiveEffect> = new Set()

 if (type === OperationTypes.CLEAR) {
  depsMap.forEach(dep => {
   addRunners(effects, computedRunners, dep)
  })
 } else {
  // 添加effect到set中
  if (key !== void 0) {
   addRunners(effects, computedRunners, depsMap.get(key as string | symbol))
  }
  // also run for iteration key on ADD | DELETE
  if (type === OperationTypes.ADD || type === OperationTypes.DELETE) {
   const iterationKey = Array.isArray(target) ? 'length' : ITERATE_KEY
   addRunners(effects, computedRunners, depsMap.get(iterationKey))
  }
 }

 // 执行set中的effect
 const run = (effect: ReactiveEffect) => {
  scheduleRun(effect, target, type, key, extraInfo)
 }
 computedRunners.forEach(run)
 effects.forEach(run)
}

看到这个函数开始的targetMap,大家应该很清楚要干嘛了吧,没错,拿到对象的Map,它包含了属性的所有依赖

  1. 如果没有Map直接返回
  2. 创建了两个Set,要干嘛用呢
// 用来保存将要执行的依赖
 const effects: Set<ReactiveEffect> = new Set()
 // computed依赖,因为trigger不仅是要处理effect,watch,还要处理computed惰性求值的情况
 const computedRunners: Set<ReactiveEffect> = new Set()

处理三种情况CLEAR,ADD,DELETE,SET(这里没有标识)

// effect set
const effects: Set<ReactiveEffect> = new Set()
// computed effect set
const computedRunners: Set<ReactiveEffect> = new Set()

function addRunners(
 effects: Set<ReactiveEffect>,
 computedRunners: Set<ReactiveEffect>,
 effectsToAdd: Set<ReactiveEffect> | undefined
) {
 if (effectsToAdd !== void 0) {
  effectsToAdd.forEach(effect => {
   if (effect.computed) {
    computedRunners.add(effect)
   } else {
    effects.add(effect)
   }
  })
 }
}

可以看到,三种情况实际上都差不多,唯一的区别就是,如果添加的对象是数组,就会拿到length属性的依赖,用于修改数组长度

if (type === OperationTypes.ADD || type === OperationTypes.DELETE) {
  const iterationKey = Array.isArray(target) ? 'length' : ITERATE_KEY
  addRunners(effects, computedRunners, depsMap.get(iterationKey))
}

执行属性对应的依赖

// 执行set中的effect
 const run = (effect: ReactiveEffect) => {
  scheduleRun(effect, target, type, key, extraInfo)
 }

 computedRunners.forEach(run)
 effects.forEach(run)
function scheduleRun(
 effect: ReactiveEffect,
 target: any,
 type: OperationTypes,
 key: string | symbol | undefined,
 extraInfo: any
) {
 if (__DEV__ && effect.onTrigger) {
  effect.onTrigger(
   extend(
    {
     effect,
     target,
     key,
     type
    },
    extraInfo // { oldValue, newValue: value }
   )
  )
 }
 if (effect.scheduler !== void 0) {
  effect.scheduler(effect)
 } else {
  effect()
 }
}

最后调用了scheduleRun,它内部会分别执行onTrigger,scheduler,effect

需要注意的是,只有开发环境才会执行onTrigger,这也是为什么,前面要这么判断

if (__DEV__) {
  const extraInfo = { oldValue, newValue: value }
  if (!hadKey) {
    trigger(target, OperationTypes.ADD, key, extraInfo)
  } else if (value !== oldValue) {
    trigger(target, OperationTypes.SET, key, extraInfo)
  }
}

readonly

有了前面的基础,readonly看起来会非常简单,唯一的区别就是rawToReadonly,rawToReadonly, readonlyHandlers

export function readonly(target: object) {

 if (reactiveToRaw.has(target)) {
  target = reactiveToRaw.get(target)
 }
 return createReactiveObject(
  target,
  rawToReadonly,
  readonlyToRaw,
  readonlyHandlers,
  readonlyCollectionHandlers
 )
}

前两个大家应该能猜出来了,关键是最后这个readonlyHandlers,区别就在set

set(target: any, key: string | symbol, value: any, receiver: any): boolean {
  if (LOCKED) {
   if (__DEV__) {
    console.warn(
     `Set operation on key "${key as any}" failed: target is readonly.`,
     target
    )
   }
   return true
  } else {
   return set(target, key, value, receiver)
  }
 }

它的实现很简单,不过LOCKED有是什么鬼,大家可以找到lock.ts

//vue-next\packages\reactivity\src\lock.ts
export let LOCKED = true

export function lock() {
 LOCKED = true
}

export function unlock() {
 LOCKED = false
}

看似简单,但是却非常重要,它能够控制被readonly的对象能够暂时被更改,就比如我们常用的props,它是无法被修改的,但是Vue内部又要对他进行更新,那怎么办,话不多说,我们再源码中看他具体应用

// vue-next\packages\runtime-core\src\componentProps.ts
export function resolveProps(
 instance: ComponentInternalInstance,
 rawProps: any,
 _options: ComponentPropsOptions | void
) {
 const hasDeclaredProps = _options != null
 const options = normalizePropsOptions(_options) as NormalizedPropsOptions
 if (!rawProps && !hasDeclaredProps) {
  return
 }
 const props: any = {}
 let attrs: any = void 0

 const propsProxy = instance.propsProxy
 const setProp = propsProxy
  ? (key: string, val: any) => {
    props[key] = val
    propsProxy[key] = val
   }
  : (key: string, val: any) => {
    props[key] = val
   }

 unlock()
 
 // 省略一些修改props操作。。
  
 lock()

 instance.props = __DEV__ ? readonly(props) : props
 instance.attrs = options
  ? __DEV__ && attrs != null
   ? readonly(attrs)
   : attrs
  : instance.props
}

这里前后分别调用了unlock和lock,这样就可以控制对readonly属性的修改

那么readonly的讲解就到这了

computed

export function computed<T>(
 getterOrOptions: (() => T) | WritableComputedOptions<T>
): any {
 const isReadonly = isFunction(getterOrOptions)
 const getter = isReadonly
  ? (getterOrOptions as (() => T))
  : (getterOrOptions as WritableComputedOptions<T>).get
 const setter = isReadonly
  ? null
  : (getterOrOptions as WritableComputedOptions<T>).set

 let dirty: boolean = true
 let value: any = undefined

 const runner = effect(getter, {
  lazy: true,
  computed: true,
  scheduler: () => {
   dirty = true
  }
 })
 return {
  _isRef: true,
  // expose effect so computed can be stopped
  effect: runner,
  get value() {
   if (dirty) {
    value = runner()
    dirty = false
   }
   trackChildRun(runner)
   return value
  },
  set value(newValue) {
   if (setter) {
    setter(newValue)
   } else {
    // TODO warn attempting to mutate readonly computed value
   }
  }
 }
}

首先是前面这段

const isReadonly = isFunction(getterOrOptions)
 const getter = isReadonly
  ? (getterOrOptions as (() => T))
  : (getterOrOptions as WritableComputedOptions<T>).get
 const setter = isReadonly
  ? null
  : (getterOrOptions as WritableComputedOptions<T>).set

大家都知道computed是可以单独写一个函数,或者get,set访问的,这里不多讲

然后调用了effect,这里lazy设置为true, scheduler可以更改dirty为true

const runner = effect(getter, {
  lazy: true,
  computed: true,
  scheduler: () => {
    dirty = true
  }
})

然后我们具体来看看,返回的对象

{
  _isRef: true,
  // expose effect so computed can be stopped
  effect: runner,
  get value() {
   if (dirty) {
    value = runner()
    dirty = false
   }
   trackChildRun(runner)
   return value
  },
  set value(newValue) {
   if (setter) {
    setter(newValue)
   } else {
    // TODO warn attempting to mutate readonly computed value
   }
  }
 }

先说说set吧,尤大似乎还没写完,只是单纯能修改值

然后是get,注意dirty的变化,如果computed依赖了state中的值,初次渲染时,他会调用依赖,然后dirty = false,关键来了,最后执行了trackChildRun

function trackChildRun(childRunner: ReactiveEffect) {
 const parentRunner =
  activeReactiveEffectStack[activeReactiveEffectStack.length - 1]
 if (parentRunner) {
  for (let i = 0; i < childRunner.deps.length; i++) {
   const dep = childRunner.deps[i]
   if (!dep.has(parentRunner)) {
    dep.add(parentRunner)
    parentRunner.deps.push(dep)
   }
  }
 }
}

由于computed是依赖了state中的属性的,一旦在初始时触发了get,执行runner,就会将依赖收集到activeReactiveEffectStack中,最后才是自己的依赖,栈的顶部是state属性的依赖

if (!dep.has(parentRunner)) {
  dep.add(parentRunner)
  parentRunner.deps.push(dep)
}

所以最后这段代码实现了state属性变化后,才导致了computed依赖的调用,从而惰性求值

ref

const convert = (val: any): any => (isObject(val) ? reactive(val) : val)
export function ref<T>(raw: T): Ref<T> {
 raw = convert(raw)
 const v = {
  _isRef: true,
  get value() {
   track(v, OperationTypes.GET, '')
   return raw
  },
  set value(newVal) {
   raw = convert(newVal)
   trigger(v, OperationTypes.SET, '')
  }
 }
 return v as Ref<T>
}

ref的实现真的很简单了,前面已经学习了那么多,相信大家都能看懂了,区别就是convert(raw)对传入的值进行了简单判断,如果是对象就设置为响应式,否则返回原始值。

最后

终于分析完了,Vue3.0响应系统使用了Proxy相比于Vue2.0的代码真的简洁许多,也好理解,说难不难。其实还有watch并没有讲,它没有在reactivity中,但是实现还是使用了effect,套路都是一样的。最后谢谢大家观看。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JavaScript版代码高亮
Jun 26 Javascript
用javascript实现的仿Flash广告图片轮换效果
Apr 24 Javascript
jQuery获得子元素个数的方法
Apr 14 Javascript
好好了解一下Cookie(强烈推荐)
Jun 14 Javascript
AnjularJS中$scope和$rootScope的区别小结
Sep 18 Javascript
JavaScript Base64 作为文件上传的实例代码解析
Feb 14 Javascript
canvas实现简易的圆环进度条效果
Feb 28 Javascript
JS回调函数基本定义与用法实例分析
May 24 Javascript
vue文件运行的方法教学
Feb 12 Javascript
Vue Element UI + OSS实现上传文件功能
Jul 31 Javascript
Vue中登录验证成功后保存token,并每次请求携带并验证token操作
Sep 08 Javascript
JavaScript实现简易计算器小功能
Oct 22 Javascript
微信小程序 textarea 层级过高问题简单解决方案
Oct 14 #Javascript
vue的路由映射问题及解决方案
Oct 14 #Javascript
浅谈Vue为什么不能检测数组变动
Oct 14 #Javascript
为什么Vue3.0使用Proxy实现数据监听(defineProperty表示不背这个锅)
Oct 14 #Javascript
Vue3.0中的monorepo管理模式的实现
Oct 14 #Javascript
Vue3 源码导读(推荐)
Oct 14 #Javascript
基于JS实现父组件的请求服务过程解析
Oct 14 #Javascript
You might like
php 数组排序 array_multisort与uasort的区别
2011/03/24 PHP
动手学习无线电
2021/03/10 无线电
基于jquery的获取mouse坐标插件的实现代码
2010/04/01 Javascript
javascript Window及document对象详细整理
2011/01/12 Javascript
基于jquery的合并table相同单元格的插件(精简版)
2011/04/05 Javascript
jQuery之ajax技术的详细介绍
2013/06/19 Javascript
javascript字母大小写转换的4个函数详解
2014/05/09 Javascript
JavaScript ES5标准中新增的Array方法
2016/06/28 Javascript
HTML页面,测试JS对C函数的调用简单实例
2016/08/09 Javascript
AngularJS中控制器函数的定义与使用方法示例
2017/10/10 Javascript
webpack自动打包和热更新的实现方法
2019/06/24 Javascript
js动态获取时间的方法分析
2019/08/02 Javascript
js+html实现点名系统功能
2019/11/05 Javascript
Vue.directive 实现元素scroll逻辑复用
2019/11/29 Javascript
vue addRoutes路由动态加载操作
2020/08/04 Javascript
vue v-on:click传递动态参数的步骤
2020/09/11 Javascript
Vue 的 v-model用法实例
2020/11/23 Vue.js
用python登录Dr.com思路以及代码分享
2014/06/25 Python
自己使用总结Python程序代码片段
2015/06/02 Python
以视频爬取实例讲解Python爬虫神器Beautiful Soup用法
2016/01/20 Python
python 第三方库的安装及pip的使用详解
2017/05/11 Python
python爬虫正则表达式之处理换行符
2018/06/08 Python
python中csv文件的若干读写方法小结
2018/07/04 Python
解决Python下json.loads()中文字符出错的问题
2018/12/19 Python
Django利用cookie保存用户登录信息的简单实现方法
2019/05/27 Python
大专生自荐信
2013/10/04 职场文书
前台文员岗位职责及工作流程
2013/11/19 职场文书
护理专业自我鉴定
2014/01/30 职场文书
优秀团队获奖感言
2014/02/19 职场文书
优秀团员事迹材料1000字
2014/08/20 职场文书
团党委领导干部党的群众路线教育实践活动个人对照检查材料思想汇
2014/10/05 职场文书
2014年度安全工作总结
2014/12/04 职场文书
农民工工资保障承诺书
2015/05/04 职场文书
2016感恩母亲节校园广播稿
2015/12/17 职场文书
护士爱岗敬业心得体会
2016/01/25 职场文书
竞聘演讲报告:基本写作有哪些?附开头范文
2019/10/16 职场文书