经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » Vue.js » 查看文章
Vue3.0 响应式系统源码逐行分析讲解
来源:jb51  时间:2019/10/14 12:24:12  对本文有异议

前言

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

  • effect
  • reactive
  • readonly
  • computed
  • ref

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

引子

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

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  7. <title>Document</title>
  8. <script src="../packages/vue/dist/vue.global.js"></script>
  9. </head>
  10. <body>
  11. <div id="app"></div>
  12. <script>
  13. const { reactive, computed, effect, watch, createApp } = Vue
  14. const App = {
  15. template: `
  16. <div id="box">
  17. <button @click="increment">{{ state.count }}</button>
  18. </div>
  19. `,
  20. setup() {
  21. const state = reactive({
  22. count: 0
  23. })
  24. function increment(e) {
  25. state.count++
  26. }
  27. effect(() => {
  28. console.log('count改变', state.count);
  29. })
  30. return {
  31. state,
  32. increment
  33. }
  34. }
  35. }
  36. createApp().mount(App, '#app')
  37. </script>
  38. </body>
  39. </html>

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

effect

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

上源码

  1. export function effect(
  2. fn: Function,
  3. options: ReactiveEffectOptions = EMPTY_OBJ
  4. ): ReactiveEffect {
  5. if ((fn as ReactiveEffect).isEffect) {
  6. fn = (fn as ReactiveEffect).raw
  7. }
  8. const effect = createReactiveEffect(fn, options)
  9. if (!options.lazy) {
  10. effect()
  11. }
  12. return effect
  13. }

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

调用createReactiveEffect

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

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

返回effect

createReactiveEffect

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

  1. function createReactiveEffect(
  2. fn: Function,
  3. options: ReactiveEffectOptions
  4. ): ReactiveEffect {
  5. // 又包装了一层函数
  6. const effect = function effect(...args): any {
  7. return run(effect as ReactiveEffect, fn, args)
  8. } as ReactiveEffect
  9. effect.isEffect = true // 标识effect
  10. effect.active = true // 如果active
  11. effect.raw = fn // 传入的回调
  12. effect.scheduler = options.scheduler
  13. effect.onTrack = options.onTrack
  14. effect.onTrigger = options.onTrigger
  15. effect.onStop = options.onStop
  16. effect.computed = options.computed
  17. effect.deps = [] // 用于收集依赖
  18. return effect
  19. }

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

  1. function run(effect: ReactiveEffect, fn: Function, args: any[]): any {
  2. if (!effect.active) {
  3. return fn(...args)
  4. }
  5. if (activeReactiveEffectStack.indexOf(effect) === -1) {
  6. cleanup(effect)
  7. try {
  8. activeReactiveEffectStack.push(effect)
  9. return fn(...args)
  10. } finally {
  11. activeReactiveEffectStack.pop()
  12. }
  13. }
  14. }

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

怎么样,是不是很简单~

reactive

  1. export function reactive(target: object) {
  2. // 如果target是已经被readonly对象,那么直接返回对应的proxy对象
  3. if (readonlyToRaw.has(target)) {
  4. return target
  5. }
  6.  
  7. // 如果target是已经被readonly对象,那么直接返回对应的真实对象
  8. if (readonlyValues.has(target)) {
  9. return readonly(target)
  10. }
  11. return createReactiveObject(
  12. target,
  13. rawToReactive,
  14. reactiveToRaw,
  15. mutableHandlers,
  16. mutableCollectionHandlers
  17. )
  18. }
  19.  

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

  1. // 情况一
  2. const state1 = readonly({ count: 0 })
  3. const state2 = reactive(state1)
  4.  
  5. // 情况二
  6. const obj = { count: 0 }
  7. const state1 = readonly(obj)
  8. const state2 = reactive(obj)
  9. 可以看到reactive它的参数是被readonly的对象,reactive不会对它再次创建响应式,而是通过Map映射,拿到对应的对象,即Proxy <==> Object的相互转换。
  10.  
  11.  
  12. createReactiveObject创建响应式对象,注意它的参数
  13. createReactiveObject(
  14. target,
  15. rawToReactive, // Object ==> Proxy
  16. reactiveToRaw, // Proxy ==> Object
  17. mutableHandlers, // get set has ...
  18. mutableCollectionHandlers // 很少会用,不讲了~
  19. )

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

createReactiveObject

  1. function createReactiveObject(
  2. target: any,
  3. toProxy: WeakMap<any, any>,
  4. toRaw: WeakMap<any, any>,
  5. baseHandlers: ProxyHandler<any>,
  6. collectionHandlers: ProxyHandler<any>
  7. ) {
  8. // 如果不是对象,在开发环境报出警告
  9. if (!isObject(target)) {
  10. if (__DEV__) {
  11. console.warn(`value cannot be made reactive: ${String(target)}`)
  12. }
  13. return target
  14. }
  15.  
  16. let observed = toProxy.get(target)
  17. // 如果目标对象已经有proxy对象,直接返回
  18. if (observed !== void 0) {
  19. return observed
  20. }
  21.  
  22. // 如果目标对象是proxy的对象,并且有对应的真实对象,那么也直接返回
  23. if (toRaw.has(target)) {
  24. return target
  25. }
  26. // 如果它是vnode或者vue,则不能被观测
  27. if (!canObserve(target)) {
  28. return target
  29. }
  30. // 判断被观测的对象是否是set,weakSet,map,weakMap,根据情况使用对应proxy的,配置对象
  31. const handlers = collectionTypes.has(target.constructor)
  32. ? collectionHandlers
  33. : baseHandlers
  34. observed = new Proxy(target, handlers)
  35. toProxy.set(target, observed)
  36. toRaw.set(observed, target)
  37. if (!targetMap.has(target)) {
  38. targetMap.set(target, new Map())
  39. }
  40. return observed
  41. }

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

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

  1. // 这种情况
  2. const obj = { count: 0 }
  3. const state1 = reative(obj)
  4. const state2 = reative(obj)

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

  1. // 这种情况
  2. const obj = { count: 0 }
  3. const state1 = reative(obj)
  4. const state2 = reative(state1)

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

  1. const canObserve = (value: any): boolean => {
  2. return (
  3. !value._isVue &&
  4. !value._isVNode &&
  5. observableValueRE.test(toTypeString(value)) &&
  6. !nonReactiveValues.has(value)
  7. )
  8. }

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

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

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

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

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

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

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

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

依赖收集

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

  1. // vue-next\packages\runtime-core\src\createRenderer.ts
  2. function setupRenderEffect(
  3. instance: ComponentInternalInstance,
  4. parentSuspense: HostSuspsenseBoundary | null,
  5. initialVNode: HostVNode,
  6. container: HostElement,
  7. anchor: HostNode | null,
  8. isSVG: boolean
  9. ) {
  10. // create reactive effect for rendering
  11. let mounted = false
  12. instance.update = effect(function componentEffect() {
  13. // ...
  14. }, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
  15. }

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

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

  1. function run(effect: ReactiveEffect, fn: Function, args: any[]): any {
  2. if (!effect.active) {
  3. return fn(...args)
  4. }
  5. if (activeReactiveEffectStack.indexOf(effect) === -1) {
  6. cleanup(effect)
  7. try {
  8. activeReactiveEffectStack.push(effect)
  9. return fn(...args)
  10. } finally {
  11. activeReactiveEffectStack.pop()
  12. }
  13. }
  14. }

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

  1. if (!mounted) {
  2. const subTree = (instance.subTree = renderComponentRoot(instance))
  3. // beforeMount hook
  4. if (instance.bm !== null) {
  5. invokeHooks(instance.bm)
  6. }
  7. patch(null, subTree, container, anchor, instance, parentSuspense, isSVG)
  8. initialVNode.el = subTree.el
  9. // mounted hook
  10. if (instance.m !== null) {
  11. queuePostRenderEffect(instance.m, parentSuspense)
  12. }
  13. mounted = true
  14. }

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

  1. //vue-next\packages\runtime-core\src\component.ts
  2. function finishComponentSetup(
  3. instance: ComponentInternalInstance,
  4. parentSuspense: SuspenseBoundary | null
  5. ) {
  6. const Component = instance.type as ComponentOptions
  7. if (!instance.render) {
  8. if (Component.template && !Component.render) {
  9. if (compile) {
  10. Component.render = compile(Component.template, {
  11. onError(err) {}
  12. })
  13. } else if (__DEV__) {
  14. warn(
  15. `Component provides template but the build of Vue you are running ` +
  16. `does not support on-the-fly template compilation. Either use the ` +
  17. `full build or pre-compile the template using Vue CLI.`
  18. )
  19. }
  20. }
  21. if (__DEV__ && !Component.render) {
  22. warn(
  23. `Component is missing render function. Either provide a template or ` +
  24. `return a render function from setup().`
  25. )
  26. }
  27. instance.render = (Component.render || NOOP) as RenderFunction
  28. }
  29.  
  30. // ...其他
  31. }
  32.  

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

  1. (function anonymous(
  2. ) {
  3. const _Vue = Vue
  4. const _createVNode = Vue.createVNode
  5.  
  6. const _hoisted_1 = { id: "box" }
  7.  
  8. return function render() {
  9. with (this) {
  10. const { toString: _toString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
  11. return (_openBlock(), _createBlock("div", _hoisted_1, [
  12. _createVNode("button", { onClick: increment }, _toString(state.count), 9 /* TEXT, PROPS */, ["onClick"])
  13. ]))
  14. }
  15. }
  16. })
  17.  

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

get

  1. // vue-next\packages\reactivity\src\baseHandlers.ts
  2. function createGetter(isReadonly: boolean) {
  3. return function get(target: any, key: string | symbol, receiver: any) {
  4. const res = Reflect.get(target, key, receiver)
  5. if (typeof key === 'symbol' && builtInSymbols.has(key)) {
  6. return res
  7. }
  8. // _isRef
  9. if (isRef(res)) {
  10. return res.value
  11. }
  12. track(target, OperationTypes.GET, key)
  13. // 如果该属性对应的值还是对象,就继续递归创建响应式
  14. return isObject(res)
  15. ? isReadonly
  16. ? // need to lazy access readonly and reactive here to avoid
  17. // circular dependency
  18. readonly(res)
  19. : reactive(res)
  20. : res
  21. }
  22. }

调用Reflect.get获取属性值

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

  1. // 这种情况
  2. const key = Symbol('key')
  3. const state = reative({
  4. [key]: 'symbol value'
  5. })
  6. state[key]

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

调用track

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

下面我会详细讲解

track

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

  1. target: any, // 目标对象
  2. type: OperationTypes, // 追踪数据变化类型,这里是get
  3. key?: string | symbol // 需要获取的key
  4. export const enum OperationTypes {
  5. SET = 'set',
  6. ADD = 'add',
  7. DELETE = 'delete',
  8. CLEAR = 'clear',
  9. GET = 'get',
  10. HAS = 'has',
  11. ITERATE = 'iterate'
  12. }
  13.  
  1. export function track(
  2. target: any,
  3. type: OperationTypes,
  4. key?: string | symbol
  5. ) {
  6. if (!shouldTrack) {
  7. return
  8. }
  9. // 获取activeReactiveEffectStack中的依赖
  10. const effect = activeReactiveEffectStack[activeReactiveEffectStack.length - 1]
  11. if (effect) {
  12. if (type === OperationTypes.ITERATE) {
  13. key = ITERATE_KEY
  14. }
  15. // 获取目标对象对应的依赖map
  16. let depsMap = targetMap.get(target)
  17. if (depsMap === void 0) {
  18. targetMap.set(target, (depsMap = new Map()))
  19. }
  20. // 获取对应属性的依赖
  21. let dep = depsMap.get(key as string | symbol)
  22. // 如果该依赖不存在
  23. if (!dep) {
  24. // 设置属性对应依赖
  25. depsMap.set(key as string | symbol, (dep = new Set()))
  26. }
  27. // 如果属性对应依赖set中不存在该依赖
  28. if (!dep.has(effect)) {
  29. // 添加到依赖set中
  30. dep.add(effect)
  31. effect.deps.push(dep)
  32. if (__DEV__ && effect.onTrack) {
  33. // 调用onTrack钩子
  34. effect.onTrack({
  35. effect,
  36. target,
  37. type,
  38. key
  39. })
  40. }
  41. }
  42. }
  43. }

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

如果effect存在

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

  1. const state = reative({
  2. count: 0
  3. })
  4. effect(() => {
  5. console.log(state.count)
  6. })
  7.  
  8. // 依赖大致结构(随便写的,不太规范)
  9. {
  10. target(state):Map {
  11. count: Set (componentEffect渲染依赖, user自己添加的依赖)
  12. }
  13. }
  14.  

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

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

添加依赖到Set中

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

最后调用onTrack回调

  1. // 调用onTrack钩子
  2. effect.onTrack({
  3. effect,
  4. target,
  5. type,
  6. key
  7. })

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

set

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

  1. function set(
  2. target: any,
  3. key: string | symbol,
  4. value: any,
  5. receiver: any
  6. ): boolean {
  7. value = toRaw(value)
  8. const hadKey = hasOwn(target, key)
  9. const oldValue = target[key]
  10. // 如果旧的值是ref,而新的值不是ref
  11. if (isRef(oldValue) && !isRef(value)) {
  12. // 直接更改原始ref即可
  13. oldValue.value = value
  14. return true
  15. }
  16. const result = Reflect.set(target, key, value, receiver)
  17. // don't trigger if target is something up in the prototype chain of original
  18. if (target === toRaw(receiver)) {
  19. /* istanbul ignore else */
  20. if (__DEV__) {
  21. const extraInfo = { oldValue, newValue: value }
  22. if (!hadKey) {
  23. trigger(target, OperationTypes.ADD, key, extraInfo)
  24. } else if (value !== oldValue) {
  25. trigger(target, OperationTypes.SET, key, extraInfo)
  26. }
  27. } else {
  28. if (!hadKey) {
  29. trigger(target, OperationTypes.ADD, key)
  30. } else if (value !== oldValue) {
  31. trigger(target, OperationTypes.SET, key)
  32. }
  33. }
  34. }
  35. return result
  36. }

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

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

调用Reflect.set修改值

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

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

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

trigger

  1. export function trigger(
  2. target: any,
  3. type: OperationTypes,
  4. key?: string | symbol,
  5. extraInfo?: any
  6. ) {
  7. const depsMap = targetMap.get(target)
  8. if (depsMap === void 0) {
  9. // never been tracked
  10. return
  11. }
  12. // effect set
  13. const effects: Set<ReactiveEffect> = new Set()
  14. // computed effect set
  15. const computedRunners: Set<ReactiveEffect> = new Set()
  16.  
  17. if (type === OperationTypes.CLEAR) {
  18. depsMap.forEach(dep => {
  19. addRunners(effects, computedRunners, dep)
  20. })
  21. } else {
  22. // 添加effect到set中
  23. if (key !== void 0) {
  24. addRunners(effects, computedRunners, depsMap.get(key as string | symbol))
  25. }
  26. // also run for iteration key on ADD | DELETE
  27. if (type === OperationTypes.ADD || type === OperationTypes.DELETE) {
  28. const iterationKey = Array.isArray(target) ? 'length' : ITERATE_KEY
  29. addRunners(effects, computedRunners, depsMap.get(iterationKey))
  30. }
  31. }
  32.  
  33. // 执行set中的effect
  34. const run = (effect: ReactiveEffect) => {
  35. scheduleRun(effect, target, type, key, extraInfo)
  36. }
  37. computedRunners.forEach(run)
  38. effects.forEach(run)
  39. }
  40.  

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

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

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

  1. // effect set
  2. const effects: Set<ReactiveEffect> = new Set()
  3. // computed effect set
  4. const computedRunners: Set<ReactiveEffect> = new Set()
  5.  
  6. function addRunners(
  7. effects: Set<ReactiveEffect>,
  8. computedRunners: Set<ReactiveEffect>,
  9. effectsToAdd: Set<ReactiveEffect> | undefined
  10. ) {
  11. if (effectsToAdd !== void 0) {
  12. effectsToAdd.forEach(effect => {
  13. if (effect.computed) {
  14. computedRunners.add(effect)
  15. } else {
  16. effects.add(effect)
  17. }
  18. })
  19. }
  20. }
  21.  

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

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

执行属性对应的依赖

  1. // 执行set中的effect
  2. const run = (effect: ReactiveEffect) => {
  3. scheduleRun(effect, target, type, key, extraInfo)
  4. }
  5.  
  6. computedRunners.forEach(run)
  7. effects.forEach(run)
  8.  
  1. function scheduleRun(
  2. effect: ReactiveEffect,
  3. target: any,
  4. type: OperationTypes,
  5. key: string | symbol | undefined,
  6. extraInfo: any
  7. ) {
  8. if (__DEV__ && effect.onTrigger) {
  9. effect.onTrigger(
  10. extend(
  11. {
  12. effect,
  13. target,
  14. key,
  15. type
  16. },
  17. extraInfo // { oldValue, newValue: value }
  18. )
  19. )
  20. }
  21. if (effect.scheduler !== void 0) {
  22. effect.scheduler(effect)
  23. } else {
  24. effect()
  25. }
  26. }

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

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

  1. if (__DEV__) {
  2. const extraInfo = { oldValue, newValue: value }
  3. if (!hadKey) {
  4. trigger(target, OperationTypes.ADD, key, extraInfo)
  5. } else if (value !== oldValue) {
  6. trigger(target, OperationTypes.SET, key, extraInfo)
  7. }
  8. }

readonly

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

  1. export function readonly(target: object) {
  2.  
  3. if (reactiveToRaw.has(target)) {
  4. target = reactiveToRaw.get(target)
  5. }
  6. return createReactiveObject(
  7. target,
  8. rawToReadonly,
  9. readonlyToRaw,
  10. readonlyHandlers,
  11. readonlyCollectionHandlers
  12. )
  13. }
  14.  

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

  1. set(target: any, key: string | symbol, value: any, receiver: any): boolean {
  2. if (LOCKED) {
  3. if (__DEV__) {
  4. console.warn(
  5. `Set operation on key "${key as any}" failed: target is readonly.`,
  6. target
  7. )
  8. }
  9. return true
  10. } else {
  11. return set(target, key, value, receiver)
  12. }
  13. }

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

  1. //vue-next\packages\reactivity\src\lock.ts
  2. export let LOCKED = true
  3.  
  4. export function lock() {
  5. LOCKED = true
  6. }
  7.  
  8. export function unlock() {
  9. LOCKED = false
  10. }
  11.  

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

  1. // vue-next\packages\runtime-core\src\componentProps.ts
  2. export function resolveProps(
  3. instance: ComponentInternalInstance,
  4. rawProps: any,
  5. _options: ComponentPropsOptions | void
  6. ) {
  7. const hasDeclaredProps = _options != null
  8. const options = normalizePropsOptions(_options) as NormalizedPropsOptions
  9. if (!rawProps && !hasDeclaredProps) {
  10. return
  11. }
  12. const props: any = {}
  13. let attrs: any = void 0
  14.  
  15. const propsProxy = instance.propsProxy
  16. const setProp = propsProxy
  17. ? (key: string, val: any) => {
  18. props[key] = val
  19. propsProxy[key] = val
  20. }
  21. : (key: string, val: any) => {
  22. props[key] = val
  23. }
  24.  
  25. unlock()
  26. // 省略一些修改props操作。。
  27. lock()
  28.  
  29. instance.props = __DEV__ ? readonly(props) : props
  30. instance.attrs = options
  31. ? __DEV__ && attrs != null
  32. ? readonly(attrs)
  33. : attrs
  34. : instance.props
  35. }
  36.  

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

那么readonly的讲解就到这了

computed

  1. export function computed<T>(
  2. getterOrOptions: (() => T) | WritableComputedOptions<T>
  3. ): any {
  4. const isReadonly = isFunction(getterOrOptions)
  5. const getter = isReadonly
  6. ? (getterOrOptions as (() => T))
  7. : (getterOrOptions as WritableComputedOptions<T>).get
  8. const setter = isReadonly
  9. ? null
  10. : (getterOrOptions as WritableComputedOptions<T>).set
  11.  
  12. let dirty: boolean = true
  13. let value: any = undefined
  14.  
  15. const runner = effect(getter, {
  16. lazy: true,
  17. computed: true,
  18. scheduler: () => {
  19. dirty = true
  20. }
  21. })
  22. return {
  23. _isRef: true,
  24. // expose effect so computed can be stopped
  25. effect: runner,
  26. get value() {
  27. if (dirty) {
  28. value = runner()
  29. dirty = false
  30. }
  31. trackChildRun(runner)
  32. return value
  33. },
  34. set value(newValue) {
  35. if (setter) {
  36. setter(newValue)
  37. } else {
  38. // TODO warn attempting to mutate readonly computed value
  39. }
  40. }
  41. }
  42. }
  43.  

首先是前面这段

  1. const isReadonly = isFunction(getterOrOptions)
  2. const getter = isReadonly
  3. ? (getterOrOptions as (() => T))
  4. : (getterOrOptions as WritableComputedOptions<T>).get
  5. const setter = isReadonly
  6. ? null
  7. : (getterOrOptions as WritableComputedOptions<T>).set

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

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

  1. const runner = effect(getter, {
  2. lazy: true,
  3. computed: true,
  4. scheduler: () => {
  5. dirty = true
  6. }
  7. })

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

  1. {
  2. _isRef: true,
  3. // expose effect so computed can be stopped
  4. effect: runner,
  5. get value() {
  6. if (dirty) {
  7. value = runner()
  8. dirty = false
  9. }
  10. trackChildRun(runner)
  11. return value
  12. },
  13. set value(newValue) {
  14. if (setter) {
  15. setter(newValue)
  16. } else {
  17. // TODO warn attempting to mutate readonly computed value
  18. }
  19. }
  20. }

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

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

  1. function trackChildRun(childRunner: ReactiveEffect) {
  2. const parentRunner =
  3. activeReactiveEffectStack[activeReactiveEffectStack.length - 1]
  4. if (parentRunner) {
  5. for (let i = 0; i < childRunner.deps.length; i++) {
  6. const dep = childRunner.deps[i]
  7. if (!dep.has(parentRunner)) {
  8. dep.add(parentRunner)
  9. parentRunner.deps.push(dep)
  10. }
  11. }
  12. }
  13. }

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

  1. if (!dep.has(parentRunner)) {
  2. dep.add(parentRunner)
  3. parentRunner.deps.push(dep)
  4. }

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

ref

  1. const convert = (val: any): any => (isObject(val) ? reactive(val) : val)
  2. export function ref<T>(raw: T): Ref<T> {
  3. raw = convert(raw)
  4. const v = {
  5. _isRef: true,
  6. get value() {
  7. track(v, OperationTypes.GET, '')
  8. return raw
  9. },
  10. set value(newVal) {
  11. raw = convert(newVal)
  12. trigger(v, OperationTypes.SET, '')
  13. }
  14. }
  15. return v as Ref<T>
  16. }

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

最后

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

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

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号