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 相关文章推荐
jQuery EasyUI中对表格进行编辑的实现代码
Jun 10 Javascript
颜色选择器 Color Picker,IE,Firefox,Opera,Safar
Nov 25 Javascript
利用Keydown事件阻止用户输入实现代码
Mar 11 Javascript
node.js不得不说的12点内容
Jul 14 Javascript
JavaScript中判断整字类型最简洁的实现方法
Nov 08 Javascript
js实现缓冲运动效果的方法
Apr 10 Javascript
分享9点个人认为比较重要的javascript 编程技巧
Apr 27 Javascript
通过BootStrap实现轮播图的实际应用
Sep 26 Javascript
基于angular中的重要指令详解($eval,$parse和$compile)
Oct 21 Javascript
js前端解决跨域问题的8种方案(最新最全)
Nov 18 Javascript
JavaScript注入漏洞的原理及防范(详解)
Dec 04 Javascript
Angular实现图片裁剪工具ngImgCrop实践
Aug 17 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生成动态验证码gif图片
2015/10/19 PHP
php读取txt文件并将数据插入到数据库
2016/02/23 PHP
php抛出异常与捕捉特定类型的异常详解
2016/10/26 PHP
phalcon model在插入或更新时会自动验证非空字段的解决办法
2016/12/29 PHP
javascript的事件描述
2006/09/08 Javascript
新浪刚打开页面出来的全屏广告代码
2007/04/02 Javascript
100个不能错过的实用JS自定义函数
2014/03/05 Javascript
使用jquery.validate自定义方法实现&quot;手机号码或者固话至少填写一个&quot;的逻辑验证
2014/09/01 Javascript
JavaScript中双叹号!!作用示例介绍
2014/09/21 Javascript
一个js过滤空格的小函数
2014/10/10 Javascript
Javascript编写俄罗斯方块思路及实例
2015/07/07 Javascript
JS Array.slice 截取数组的实现方法
2016/01/02 Javascript
js判断鼠标位置是否在某个div中的方法
2016/02/26 Javascript
electron demo项目npm install安装失败的解决方法
2018/02/06 Javascript
详解KOA2如何手写中间件(装饰器模式)
2018/10/11 Javascript
使用JavaScript解析URL的方法示例
2019/03/01 Javascript
vue 使用高德地图vue-amap组件过程解析
2019/09/07 Javascript
微信小程序多列表渲染数据开关互不影响的实现
2020/06/05 Javascript
javascript canvas封装动态时钟
2020/09/30 Javascript
Python基于pillow判断图片完整性的方法
2016/09/18 Python
python监控进程脚本
2018/04/12 Python
python selenium自动上传有赞单号的操作方法
2018/07/05 Python
Python利用heapq实现一个优先级队列的方法
2019/02/03 Python
Python数据类型之Number数字操作实例详解
2019/05/08 Python
django序列化serializers过程解析
2019/12/14 Python
parser.add_argument中的action使用
2020/04/20 Python
Python pickle模块常用方法代码实例
2020/10/10 Python
世界上最大的汽车共享网站:Zipcar
2017/01/14 全球购物
世界上最值得信赖的多日游在线市场:TourRadar
2018/07/20 全球购物
服装电子商务创业计划书
2014/01/30 职场文书
校园文化建设方案
2014/02/03 职场文书
国家领导干部党的群众路线教育实践活动批评与自我批评材料
2014/09/23 职场文书
2014年残联工作总结
2014/11/21 职场文书
师德培训心得体会2016
2016/01/09 职场文书
找规律教学反思
2016/02/23 职场文书
使用Java去实现超市会员管理系统
2022/03/18 Java/Android