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 Ajax 仿AjaxPro.Utility.RegisterTypeForAjax辅助方法
Sep 27 Javascript
基于jquery的文本框与autocomplete结合使用(asp.net+json)
May 30 Javascript
JavaScript的继承的封装介绍
Oct 15 Javascript
jQuery实现移动 和 渐变特效的点击事件
Feb 26 Javascript
浅谈JavaScript的函数及作用域
Dec 30 Javascript
JavaScript实现动态增删表格的方法
Mar 09 Javascript
JS得到当前时间的方法示例
Mar 24 Javascript
基于js中的原型(全面讲解)
Sep 19 Javascript
Vue利用路由钩子token过期后跳转到登录页的实例
Oct 26 Javascript
JavaScript实现单例模式实例分享
Dec 22 Javascript
vue通过点击事件读取音频文件的方法
May 30 Javascript
Vue核心概念Action的总结
Jan 18 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
E路文章系统PHP
2006/12/11 PHP
url decode problem 解决方法
2011/12/26 PHP
php时区转换转换函数
2014/01/07 PHP
PHP 面向对象程序设计(oop)学习笔记(三) - 单例模式和工厂模式
2014/06/12 PHP
ThinkPHP3.2.2的插件控制器功能简述
2014/07/09 PHP
php生成图片验证码的方法
2016/04/15 PHP
PHP的RSA加密解密方法以及开发接口使用
2018/02/11 PHP
PHP基于面向对象实现的留言本功能实例
2018/04/04 PHP
PHP使用openssl扩展实现加解密方法示例
2020/02/20 PHP
对象特征检测法判断浏览器对javascript对象的支持
2009/07/25 Javascript
JQuery工具函数汇总
2015/06/15 Javascript
jQuery 获取页面li数组并删除不在数组中的key
2016/08/02 Javascript
解析微信JS-SDK配置授权,实现分享接口
2016/12/09 Javascript
使用UrlConnection实现后台模拟http请求的简单实例
2017/01/04 Javascript
jQuery Datatables表头不对齐的解决办法
2017/11/27 jQuery
Vue中mintui的field实现blur和focus事件的方法
2018/08/25 Javascript
webpack4.x CommonJS模块化浅析
2018/11/09 Javascript
JS中的算法与数据结构之字典(Dictionary)实例详解
2019/08/20 Javascript
js实现简单五子棋游戏
2020/05/28 Javascript
echarts柱状图背景重叠组合而非并列的实现代码
2020/12/10 Javascript
Vue项目中使用mock.js的完整步骤
2021/01/12 Vue.js
Python读写Excel文件的实例
2013/11/01 Python
使用C语言扩展Python程序的简单入门指引
2015/04/14 Python
Python自定义线程类简单示例
2018/03/23 Python
Python字符串匹配之6种方法的使用详解
2019/04/08 Python
分享30个新鲜的CSS3打造的精美绚丽效果(附演示下载)
2012/12/28 HTML / CSS
详解Canvas 跨域脱坑实践
2018/11/07 HTML / CSS
购买英国原创艺术:Art Gallery
2018/08/25 全球购物
会计专业个人求职信范文
2014/01/08 职场文书
物流仓储计划书
2014/01/10 职场文书
食品行业求职人的自我评价
2014/01/19 职场文书
表决心的诗句大全
2014/03/11 职场文书
协议书样本
2014/04/23 职场文书
中国梦演讲稿范文
2014/08/28 职场文书
地理科学专业自荐信
2014/09/01 职场文书
化工实习心得体会
2014/09/09 职场文书