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 相关文章推荐
使用Mootools动态添加Css样式表代码,兼容各浏览器
Dec 12 Javascript
Javascript中自动切换焦点实现代码
Dec 15 Javascript
js仿百度贴吧验证码特效实例代码
Jan 16 Javascript
Node.js事件循环(Event Loop)和线程池详解
Jan 28 Javascript
JQuery使用$.ajax和checkbox实现下次不在通知功能
Apr 16 Javascript
Jquery动态添加输入框的方法
May 29 Javascript
Javascript中判断对象是否为空
Jun 10 Javascript
jQuery遍历json的方法分析
Apr 16 Javascript
js日期相关函数dateAdd,dateDiff,dateFormat等介绍
Sep 24 Javascript
微信小程序 canvas API详解及实例代码
Oct 08 Javascript
浅谈JS的原型和继承
May 08 Javascript
JavaScript运行机制实例分析
Apr 11 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中数字、字符与对象判断函数用法实例
2014/11/26 PHP
从JavaScript的函数重名看其初始化方式
2007/03/08 Javascript
ajax java 实现自动完成功能
2012/12/19 Javascript
jquery实现弹出窗口效果的实例代码
2013/11/28 Javascript
两个多选select(multiple左右)添加、删除选项和取值实例
2014/05/12 Javascript
JavaScript实现的使用键盘控制人物走动实例
2014/08/27 Javascript
JavaScript立即执行函数的三种不同写法
2014/09/05 Javascript
javascript实现分栏显示小技巧附图
2014/10/13 Javascript
jQuery中clearQueue()方法用法实例
2014/12/29 Javascript
原生JS实现美图瀑布流布局赏析
2015/09/07 Javascript
JavaScript的继承实现小结
2017/05/07 Javascript
基于casperjs和resemble.js实现一个像素对比服务详解
2018/01/10 Javascript
vue项目实现github在线预览功能
2018/06/20 Javascript
详解keep-alive + vuex 让缓存的页面灵活起来
2019/04/19 Javascript
vue项目中在外部js文件中直接调用vue实例的方法比如说this
2019/04/28 Javascript
cordova+vue+webapp使用html5获取地理位置的方法
2019/07/06 Javascript
JavaScript中的null和undefined用法解析
2019/09/30 Javascript
Django框架中render_to_response()函数的使用方法
2015/07/16 Python
Python入门之三角函数全解【收藏】
2017/11/08 Python
Django 路由层URLconf的实现
2019/12/30 Python
python 回溯法模板详解
2020/02/26 Python
pymysql之cur.fetchall() 和cur.fetchone()用法详解
2020/05/15 Python
教你一分钟在win10终端成功安装Pytorch的方法步骤
2021/01/28 Python
用HTML5实现鼠标滚轮事件放大缩小图片的功能
2015/06/25 HTML / CSS
Answear匈牙利:来自全球200多个知名时尚品牌
2017/04/21 全球购物
Furla官网:意大利著名的皮革品牌
2019/08/06 全球购物
在C语言中"指针和数组等价"到底是什么意思?
2014/03/24 面试题
2014年道德讲堂实施方案
2014/03/05 职场文书
竞聘演讲稿怎么写
2014/08/28 职场文书
2014年副班长工作总结
2014/12/10 职场文书
自主招生自荐信格式
2015/03/04 职场文书
2015年感恩父亲节演讲稿
2015/03/19 职场文书
2015年小学教师培训工作总结
2015/07/21 职场文书
Redis分布式锁Redlock的实现
2021/08/07 Redis
Python OpenCV形态学运算示例详解
2022/04/07 Python
Python使用pyecharts控件绘制图表
2022/06/05 Python