前言 在上篇文章中——Vue3响应式原理],我们基本了解了响应式的核心reactive
和effect
,话不多说,我们这节课将剩下的内容写完。
其实watch
和watchEffect
并不在reactivity
响应式模块里,而是在runtime-dom
模块里,那为啥还要在reactivity
这个响应式模块中,来介绍这两个API
呢?一是因为这两个API
我们在项目中太常见,二才是最主要的,是因为watch
和watchEffect
都是基于上篇文章说的effect
进行了封装,从而得到的。所以说么,effect
是最底层的方法,弄懂了上篇文章的内容,那么这篇文章就显得相对好理解很多。
watch
的实现我们先在reactive.ts
和/shared/src/index.ts
中完善两个工具方法,方便我们在实现watch
时进行导入调用。
1 2 3 4 5 6 7 8 9 10 export function isReactive (value ) { return !!(value && value[ReactiveFlags .IS_REACTIVE ]) } export const isFunction = value => { return value && typeof value === 'function' }
然后在/reactivity/src
目录下新建apiWatch.ts
文件,来写watch
的主逻辑。首先我们简单回顾下Vue3
中watch
的常见用法:
1 2 3 4 5 6 7 8 9 const state = reactive ({name : '张三' , age : 18 })watch (() => state.name , (newV, oldV ) => { console .log (newV, oldV) }) watch (state, (newV, oldV ) => { console .log (newV, oldV) })
那么在用到watch
的时候,第一个参数我们可以传入一个函数(如用法1)来监听某个属性的变化,有朋友可能会问,为啥要写成一个函数,我直接把第一个参数传入state.name
不行么?醒醒,快醒醒!在这个案例中state.name
就是个定死的值张三
,监听常量,肯定是不会发生变化的啊;
同样,第一个参数还可以传入一个对象(如方法2)但是这种有几个问题,一般不推荐,比如当第一个参数传入的是对象,实际上watch
监听的是这个对象的引用地址,所以,无法区分newV
和oldV
,引用的地址是一直不变的,所以打印的结果会发现,这俩值是一样的,都是最新的值。还有个小问题就是,虽然你传入参数的是一个对象,但是在watch
方法的内部,依旧是遍历了这个对象所有的key
,并且进行取值操作(为的是触发依赖收集)。所以会对性能有所损耗,不过有时候为了方便,还是可以这么去干的(反正内部针对这种情况做了处理,代码写的爽就行了,管他呢)。
我们接下来实现watch
的逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import { isReactive } from './reactive' import { isFunction, isObject } from '@vue/shared' import { ReactiveEffect } from './effect' function traverse (value, seen = new Set () ) { if (!isObject (value)) { return value } if (seen.has (value)) { return value } seen.add (value) for (const key in value) { if (value.hasOwnProperty (key)) { traverse (value[key], seen) } } return value } export function watch (source, cb, { immediate } = {} as any ) { let getter if (isReactive (source)) { getter = () => traverse (source) } else if (isFunction (source)) { getter = source } let oldValue const job = ( ) => { const newValue = effect.run () cb (newValue, oldValue) oldValue = newValue } const effect = new ReactiveEffect (getter, job) if (immediate) { job () } oldValue = effect.run () }
首版代码就算是完成了,代码虽然不多,但是为了方便理解,我们还是需要拆分每个步骤,来进行一一讲解。
步骤1:很好理解,导出的watch
,放入传参,这里第三个参数options
我们只实现immediate
的功能;
步骤2:就是上文提到的,对于传入的source
,需要进行类型判断,如果是一个函数的话,那就让getter
赋值为这个函数;如果是对象的话,那就用函数包一层。
步骤3:但是单独包一层,并不会触发依赖收集,所以就需要对这个响应式对象source
进行遍历,然后对每个key
进行取值,从而触发依赖收集;代码看上去的效果就是,只是取了下值,实际没有进行其他任何操作。为什么要包装成一个函数呢?别急,看到第4步就明白了。
步骤4:这步是不是非常熟?没错,在上篇写effect
原理的时候,我们就是通过 new ReactiveEffect(fn, options.scheduler)
进行生成的,所以,此步骤中,我们把getter
当成第一个参数进行传参,把job
当成第二个参数,也就是当响应式对象的属性发生变化时候,就会主动来调用job
方法,如果忘了,可以再去复习下上篇文章。
步骤5:new
完后,得到的effect
,我们先执行一次effect.run
方法,就能拿到最开始的返回值,记为oldValue
。
步骤6:就是步骤4中需要传入的job
方法,当响应式对象的属性,发生变化,才会执行这个方法,我们在其中调用cb
,并且传入oldValue
和newValue
,大功告成。
是不是发现,当我们理解了effect
方法原理之后,再去写watch
的实现,就变得非常简单了呢?所以说嘛effect
是底层方法,很多方法都是基于它进行封装的。
接下来,我们再介绍一个Vue3
中watch
提供的一个功能,所谓新功能,不是无缘无故就出来的,一定是为了解决相关的场景,所以才会提出的新功能,我们改动下index.html
中的示例代码,先看看如下场景,该用什么方法来解决:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <script > const state = reactive ({ name : '张三' , age : 18 }) let timmer = 4000 function getData (data ) { return new Promise ((resolve, reject ) => { setTimeout (() => { resolve (data) }, timmer -= 1000 ) }) } watch (() => state.age , async (newV, oldV) => { const result = await getData (newV) console .log (result) app.innerHTML = result }) state.age = 20 state.age = 30 state.age = 40 </script >
我们来简单说一下上边代码的含义,设想一下页面里有个输入框,每次输入内容,都会发送一个请求,我们这边模拟用户改变了3次值,所以一共发送了3次请求;第一个请求历时4秒钟能拿到返回结果,第二个请求历时3秒能拿到结果,第三个请求历时2秒能拿到结果,那么我们期望的页面显示内容,是以最后一次输入的结果为准,即页面上显示的是state.age = 40
的结果。但是根据我们现在的逻辑,会发现,页面上过2秒后确实显示的是state.age = 40
的结果,但是又过了1秒钟,state.age = 30
这个请求的结果又被显示到页面上,又过了1秒state.age = 20
的结果最终显示在了页面上,那显然不合理,我们的输入框中,最后明明是40
,但是页面显示的结果却是20
的请求结果。
所以我们此时需要来解决这个问题,我们第一反应就是,能不能在每次触发新请求的时候,屏蔽上次请求的结果呢?(注意,请求已经发送了,不能取消),这样,就能保证就算之前的请求,过了很久才拿到返回值,也不会覆盖最新的结果。那我们来在当前代码中,修改下吧!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <script > const state = reactive ({ name : '张三' , age : 18 })let timmer = 4000 let arr = []function getData (data ) { return new Promise ((resolve, reject ) => { setTimeout (() => { resolve (data) }, timmer -= 1000 ) }) } watch (() => state.age , async (newV, oldV) => { let fn while (arr.length ){ fn = arr.shift () fn () } let flag = true arr.push (function ( ){ flag = false }) const result = await getData (newV) console .log (result) flag && (app.innerHTML = result) }) state.age = 20 state.age = 30 state.age = 40 </script >
之后,我们在页面上再次打印结果,发现,页面上始终显示的是40,也就是最后state.age = 40
对应的结果。那么,我们通过在业务逻辑中,的一些代码改良,成功的解决了请求结果顺序错乱的问题。那么在Vue3
,watch
中提供了新的参数,可以把一些逻辑放在watch
的内部,从而达到和上述代码相同的效果,同样,我们先看用法,进而推导下在watch
源码中是如何实现的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <script > const state = reactive ({ name : '张三' , age : 18 }) let timmer = 4000 function getData (data ) { return new Promise ((resolve, reject ) => { setTimeout (() => { resolve (data) }, timmer -= 1000 ) }) } watch (() => state.age , async (newV, oldV, onCleanup) => { let flag = true onCleanup (() => { flag = false }) const result = await getData (newV) console .log (result) flag && (app.innerHTML = result) }) state.age = 20 state.age = 30 state.age = 40 </script >
是不是发现,代码精简了很多?我们接下来实现一下吧!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 import { isReactive } from './reactive' import { isFunction, isObject } from '@vue/shared' import { ReactiveEffect } from './effect' function traverse (value, seen = new Set () ) { if (!isObject (value)) { return value } if (seen.has (value)) { return value } seen.add (value) for (const key in value) { if (value.hasOwnProperty (key)) { traverse (value[key], seen) } } return value } export function watch (source, cb, { immediate } = {} as any ) { let getter if (isReactive (source)) { getter = () => traverse (source) } else if (isFunction (source)) { getter = source } let oldValue let cleanup const onCleanup = fn => { cleanup = fn } const job = ( ) => { if (cleanup) cleanup () const newValue = effect.run () cb (newValue, oldValue, onCleanup) oldValue = newValue } const effect = new ReactiveEffect (getter, job) if (immediate) { job () } oldValue = effect.run () }
看7、8、9三个步骤,其实就是类似于刚才我们写在外边的逻辑,只不过我们现在把这些逻辑写在了watch
内部,多读几遍,非常巧妙。
至此为止,关于watch
的核心逻辑,我们就已经写完了,是不是看起来,没有想象中的那么难呢?接下来我们还要实现下watchEffect
,莫慌,只需要改动几行代码,便可轻松实现。首先,我们将刚才导出的watch
改个名字换为doWatch
,变成一个通用函数,因为前文说过,watch
和watchEffect
都是基于effect
方法进行封装的,所以二者的逻辑可以说是非常相似的,所以我们没必要再写一遍,那么只要调用通用函数,根据传参不同,即可快速实现:
watchEffect
的实现1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 import { isReactive } from './reactive' import { isFunction, isObject } from '@vue/shared' import { ReactiveEffect } from './effect' function traverse (value, seen = new Set () ) { if (!isObject (value)) { return value } if (seen.has (value)) { return value } seen.add (value) for (const key in value) { if (value.hasOwnProperty (key)) { traverse (value[key], seen) } } return value } export function doWatch (source, cb, { immediate } = {} as any ) { let getter if (isReactive (source)) { getter = () => traverse (source) } else if (isFunction (source)) { getter = source } let oldValue let cleanup const onCleanup = fn => { cleanup = fn } const job = ( ) => { if (cb) { if (cleanup) cleanup () const newValue = effect.run () cb (newValue, oldValue, onCleanup) oldValue = newValue }else { effect.run () } } const effect = new ReactiveEffect (getter, job) if (immediate) { job () } oldValue = effect.run () } export function watch (source, cb, options ) { return doWatch (source, cb, options) } export function watchEffect (source, options ) { return doWatch (source, null , options) }
改动点仅仅是第10步骤,加了一个判断,那么这样doWatch
就是一个通用函数,只需要根据传参不同,在外边再包一层,就是我们平时中项目常用的watch
和watchEffect
了!怎样,是不是很容易?那我们继续往下看吧
computed
的实现我们还是简单用一下computed
,看看有哪几种用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <script > const state = reactive ({ name : '张三' , age : 18 })const info = computed ({ get ( ) { console .log ('我触发啦!' ) return state.name + state.age } set (val ){ console .log (val) } }) console .log (info.value )console .log (info.value )const info = computed (() => { return state.name + state.age }) </script >
回顾了下基本用法后,我们还是在reactivity/src
目录下,新建computed.ts
文件,然后在reactivity/src/index.ts
中export * from '.computed'
,进行导出。接下来,我们便可以在computed.ts
中来实现computed
的逻辑了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 import { isFunction } from '@vue/shared' import { ReactiveEffect } from './effect' class ComputedRefImpl { public effect public _value public __v_isRef = true constructor (getter, public setter ) { this .effect = new ReactiveEffect (getter, () => { }) } get value () { this ._value = this .effect .run () return this ._value } set value (newV ){ this .setter (newValue) } } export function computed (getterOrOptions ) { const isGetter = isFunction (getterOrOptions) let getter, setter if (isGetter) { getter = isGetter setter = () => ({}) } else { getter = getterOrOptions.get setter = getterOrOptions.set } return new ComputedRefImpl (getter, setter) }
我们来按步骤一一讲解下
步骤1:没错,非常熟悉的套路,和watch
处理参数的方式几乎是一模一样;
步骤2:返回一个响应式对象,获取computed
的值,需要通过.value
的方法;
步骤3:依旧是通过new ReactiveEffect
,传入getter
进行依赖收集,生成effect
实例对象;
步骤4:因为computed
返回的对象,是通过.value
来访问的,所以要创建get set
,执行相应逻辑;
至此,我们的computed
就可以简单的用起来了,我们先运行一下,其他的问题,我们后边再来解决,我们改变下index.html
的代码,查看打印结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <script > const state = reactive ({ name : '张三' , age : 18 })const info = computed ({ get ( ) { console .log ('我调用啦!' ) return state.name + state.age }, set (val) { console .log (val) } }) console .log (info.value )console .log (info.value )</script >
此时,我们会发现,控制台中打印的结果是:
1 2 3 4 复制代码我调用啦! 张三18 我调用啦! 张三18
这和我们平时用的computed
好像哪里有些不同?没错,info
中依赖的响应式对象state
中的属性,并没有变化,但是却触发了两次computed
,并没有实现缓存的效果,那么我们接下来就来实现一下吧!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import { isFunction } from '@vue/shared' import { ReactiveEffect } from './effect' class ComputedRefImpl { public effect public _value public _dirty = true constructor (getter, public setter ) { this .effect = new ReactiveEffect (getter, () => { this_dirty = true }) } get value () { if (this ._dirty ) { this ._dirty = false this ._value = this .effect .run () } return this ._value } set value (newV ){ this .setter (newValue) } } export function computed (getterOrOptions ) { const isGetter = isFunction (getterOrOptions) let getter, setter if (isGetter) { getter = isGetter setter = () => ({}) } else { getter = getterOrOptions.get setter = getterOrOptions.set } return new ComputedRefImpl (getter, setter) }
我们看捕捉5~7,是不是通过一个_dirty
属性,就实现了如果依赖不发生变化,那么就不会多次触发computed
对象中的get
了呢?还是那句话,computed
的实现,依旧是依赖于effect
,所以理解effect
才是重中之重。
看起来是没啥问题了,但是在有一种场景下,存在着问题,我们改一下index.html
代码,来看一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <script > const state = reactive ({ name : '张三' , age : 18 })const info = computed ({ get ( ) { console .log ('我调用啦!' ) return state.name + state.age }, set (val) { console .log (val) } }) effect (() => { app.innerHTML = info.value }) console .log (info.value )setTimeout (() => { state.age = 22 console .log (info.value ) }, 2000 ) </script >
没错,就是当我们在effect
方法中,使用了computed
计算属性,那么页面就不会更新,因为effect
中并没有对计算属性进行依赖收集,而computed
计算属性中也没有对应的effect
方法。那怎么实现呢?我们想一想,是不是很类似于之前写的依赖收集track
和触发更新trigger
方法呢?没错,我们只需要在computed
中增加进行依赖收集和触发更新的逻辑就好了,而这两个逻辑,我们之前也写过,所以可以把通用的代码直接copy
过来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import { isFunction } from '@vue/shared' import { ReactiveEffect , activeEffect, trackEffects, triggerEffects } from './effect' class ComputedRefImpl { public effect public _value public dep = new Set () public _dirty = true constructor (getter, public setter ) { this .effect = new ReactiveEffect (getter, () => { this_dirty = true triggerEffects (this .dep ) }) } get value () { if (activeEffect) { trackEffects (this .dep ) } if (this ._dirty ) { this ._dirty = false this ._value = this .effect .run () } return this ._value } set value (newV ){ this .setter (newValue) } } export function computed (getterOrOptions ) { const isGetter = isFunction (getterOrOptions) let getter, setter if (isGetter) { getter = isGetter setter = () => ({}) } else { getter = getterOrOptions.get setter = getterOrOptions.set } return new ComputedRefImpl (getter, setter) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // reactivity/src/effect.ts 文件 export function triggerEffects(dep) { const effects = [...dep] effects && effects.forEach(effect => { // 正在执行的effect,不要多次执行,防止死循环 if (effect !== activeEffect) { // 如果用户传入了scheduler,那么就执行用户自定义逻辑,否则还是执行run逻辑 if(effect.scheduler) { effect.scheduler() }else { effect.run() } } }) } // computed中收集effect的依赖 export function trackEffects(dep) { let shouldTrack = !dep.has(activeEffect) if(shouldTrack) { // 依赖和effect多对多关系保存 dep.add(activeEffect) activeEffect.deps.push(dep) } }
我们看步骤8,9,依赖收集和触发更新的方法,我们依旧写在effect.ts
文件中(可以对比下trigger
和track
方法,逻辑几乎一模一样)。我们再运行刚才index.html
中的代码,发现页面成功的更新了,那么至此,computed
的核心逻辑我们就写完啦!
ref
的实现我们在reactivity/src
目录下创建ref.ts
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import { isObject } from '@vue/shared' import { activeEffect, trackEffects, triggerEffects } from './effect' import { reactive } from './reactive' function toReactive (value ) { return isObject (value) ? reactive (value) : value } class RefImpl { public _value public dep = new Set () public __v_isRef = true constructor (public rawValue ) { this ._value = toReactive (rawValue) } get value () { if (activeEffect) { trackEffects (this .dep ) } return this ._value } set value (newVal) { if (newVal !== this .rawValue ) { this .rawValue = newVal this ._value = toReactive (newVal) triggerEffects (this .dep ) } } }
有了前边的基础,写起ref
来,就显得非常得心应手,核心其实就这几行代码,通过注释,我们就不难发现,如果传入的是对象,那么就是利用了之前写的reactive
进行包装处理,如果传入了其他类型的数据,那么就和computed
中的方法一模一样,需要进行依赖收集和触发更新。
实现toRef
和toRefs
这两个方法,其实我们开发中,用的会比较少,所以还是先简单介绍下用法,然后再思考下如何实现,最后再来写一下它们的原理:
1 2 3 4 5 6 7 8 9 10 11 <script > const state = reactive ({name : '张三' }))let name = state.name effect (() => { app.innerHTML = name }) setTimeout (() => { state.name = '李四' }, 1000 ) </script >
这是上边的代码可以看到,当我们将let name = state.name
单独取出来之后,再修改state.name
的值之后,name
的值就不会再发生变化了,页面上的名字也不会随之发生变化,也就是所谓的丢失响应式,那么利用toRef
就可以解决这种问题:
1 2 3 4 5 6 7 8 9 10 11 <script > const state = reactive ({name : '张三' }))let name = toRef (state, 'name' )effect (() => { app.innerHTML = name.value }) setTimeout (() => { state.name = '李四' }, 1000 ) </script >
我们来思考一下如何实现呢?为了不丢失响应式,所以就需要联系,那么肯定就是在name
和state.name
之间存在某种联系,当改变state.name
值的时候,从而能使得name
同步进行变动。既然这样,那不就可以做一层代理,当访问和修改name
的时候,实际是去访问和修改state.name
的值么?思路有了,我们便可以通过代码来实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 import { isObject } from '@vue/shared' import { activeEffect, trackEffects, triggerEffects } from './effect' import { reactive } from './reactive' function toReactive (value ) { return isObject (value) ? reactive (value) : value } class RefImpl { public _value public dep = new Set () public __v_isRef = true constructor (public rawValue ) { this ._value = toReactive (rawValue) } get value () { if (activeEffect) { trackEffects (this .dep ) } return this ._value } set value (newVal) { if (newVal !== this .rawValue ) { this .rawValue = newVal this ._value = toReactive (newVal) triggerEffects (this .dep ) } } } export function ref (value ) { return new RefImpl (value) } class ObjectRefImpl { public __v_isRef = true constructor (public _object, public _key ) { } get value () { return this ._object [this ._key ] } set value (newVal ) { this ._object [this ._key ] = newVal } } export function toRef (object , key ) { return new ObjectRefImpl (object , key) } export function toRefs (object ) { const ret = isArray (object ) ? new Array (object .length ) : {} for (const key in object ) { ret[key] = toRef (object , key) } return ret }
代码非常简单,就是进行了一次代理转化,而我们项目中常用的是toRefs
,也是遍历每个属性,并借助toRef
来实现的。
proxyRef
的实现这个方法可能听起来很陌生,但是只要写过Vue3
的项目,就一定会用到这个方法,举个例子就明白了:
1 2 3 4 5 6 <template > <div > {{ name }}</div > </template > <script > let name = ref ('张三' )</script >
当我们在代码中,用ref
声明了一个字符串类型的数据后,如果在代码中使用这个值,是不是需要通过name.value
的方式来调用呢?但是当我们在模板中使用的时候,却可以直接来用这个name
而并不需要再.value
来取值,诶,这就是Vue3
在模板编译的时候,内部调用了这个方法,帮助我们对ref
声明变量,进行自动脱钩,那么细心的朋友也发现了,不管是在computed
,还是ref
代码中,都有一样public __v_isRef = true
这个标识,没错,接下来就要用到这个标识了,这个标识就是为了在自动脱钩的时候,来进行分辨的。那么我们来实现这个proxyRef
方法吧~
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 import { isObject } from '@vue/shared' import { activeEffect, trackEffects, triggerEffects } from './effect' import { reactive } from './reactive' function toReactive (value ) { return isObject (value) ? reactive (value) : value } class RefImpl { public _value public dep = new Set () public __v_isRef = true constructor (public rawValue ) { this ._value = toReactive (rawValue) } get value () { if (activeEffect) { trackEffects (this .dep ) } return this ._value } set value (newVal) { if (newVal !== this .rawValue ) { this .rawValue = newVal this ._value = toReactive (newVal) triggerEffects (this .dep ) } } } export function ref (value ) { return new RefImpl (value) } class ObjectRefImpl { public __v_isRef = true constructor (public _object, public _key ) { } get value () { return this ._object [this ._key ] } set value (newVal ) { this ._object [this ._key ] = newVal } } export function toRef (object , key ) { return new ObjectRefImpl (object , key) } export function toRefs (object ) { const ret = isArray (object ) ? new Array (object .length ) : {} for (const key in object ) { ret[key] = toRef (object , key) } return ret } export function isRef (value ) { return !!(value && value.__v_isRef === true ) } export function proxyRefs (objectWithRefs ) { return new Proxy (objectWithRefs, { get (target, key, receiver ) { let v = Reflect .get (target, key, receiver) return isRef (v) ? v.value : v }, set (target, key, value, receiver ) { const oldValue = target[key] if (oldValue.__v_isRef ) { oldValue.value = value return true }else { return Reflect .set (target, key, value, receiver) } } }) }
这个proxyRef
方法,在后续文章中,会用到,这里只是提前介绍下这个方法。
结语 那么至此,我们reactivity
响应式模块中的一些个核心的方法,基本上已经实现了最核心的逻辑,这样,我们再去阅读源码的时候,就不会变得一头雾水了,好好再熟悉一遍reactivity
模块的方法吧,然后再去看下Vue3
源码中的reactivity
逻辑;我们接下来会继续分析Vue3
,其他模块的核心代码。
作者:柠檬soda水 链接:https://juejin.cn/post/7203268889462145061 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。