前言 心态爆炸的几天,为什么新年会这么心态爆炸QAQ
不过就算心态爆炸爆炸还是要学一下的,这篇介绍一下Vue3是怎么实现数据响应式的,Vue2的实现的方法我也写过,可以看这里:https://www.sakura-snow.com/archives/491
这篇来介绍一下Vue3的数据响应式,我们会使用我们自己手写的effect
和reactive
来模拟(因为源码不是那么好看,不过也会放一下源码)
文档:https://github.com/vuejs/docs-next
vue3:https://github.com/vuejs/vue-next
reactive和effect的实现 我们用下面的代码来进行测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let { reactive, effect, computed } = VueReactivity;let obj = { name : 'Sakura' , age : 16 , address : { a : 1 }, arr : [1 , 2 , 3 ,4 ,5 ] }let state = reactive(obj);effect(function fn1 ( ) { app.innerHTML = ` name:${state.name} age:${state.age} arr:${JSON .stringify(state.arr)} ` }); setTimeout (() => { state.age++; state.name = "Sena" ; state.arr[10 ] = "10" }, 1000 );
使用下面是reactive
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 import {isObject} from "../shared" ;import {mutableHandlers} from "./baseHandlers" ;export const reactive = (target: object ) => { return createReactiveObject(target, mutableHandlers); } const reactiveMap = new WeakMap ();function createReactiveObject (target, baseHandler ) { if (!isObject(target)) { return target; } let existProxy = reactiveMap.get(target); if (existProxy) { return existProxy; } const proxy = new Proxy (target, baseHandler); reactiveMap.set(target, proxy); return proxy; }
这里的mutableHandlers
是从外部导入的
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 {trigger} from "./effect" ;export const mutableHandlers = { get (target, key, receiver ) { let res = Reflect .get(target, key, receiver); if (typeof key == 'symbol' ) { return res; } track(target, key); if (res && res.__v_isRef) { return res.value; } return isObject(res) ? reactive(res) : res; }, set (target, key, value, receiver ) { const oldValue = target[key]; const hadKey = isArray(target) && (parseInt (key, 10 ) == key) ? Number (key) < target.length : hasOwn(target, key); let result = Reflect .set(target, key, value, receiver); if (!hadKey) { trigger(target, 'add' , key, value); } else if (hasChange(oldValue, value)) { trigger(target, 'set' , key, value); } return result } }
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 import {trigger} from "./effect" ;export const mutableHandlers = { get (target, key, receiver ) { let res = Reflect .get(target, key, receiver); if (typeof key == 'symbol' ) { return res; } track(target, key); if (res && res.__v_isRef) { return res.value; } return isObject(res) ? reactive(res) : res; }, set (target, key, value, receiver ) { const oldValue = target[key]; const hadKey = isArray(target) && (parseInt (key, 10 ) == key) ? Number (key) < target.length : hasOwn(target, key); let result = Reflect .set(target, key, value, receiver); if (!hadKey) { trigger(target, 'add' , key, value); } else if (hasChange(oldValue, value)) { trigger(target, 'set' , key, value); } return result } }
Vue3
的大致思路和Vue2
是一样的,核心就是
在getter
中收集依赖
在setter
中触发依赖
effect
设置的函数就是依赖,
然后是track
函数,这个函数可以用于将effect
和proxy对象
的属性关联起来
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 export const effect = (fn: Function , options: any = {} ) => { const effect = createReactiveEffect(fn, options); if (!options.lazy) { effect(); } return effect; } export let effectStack = []; export let activeEffect = null ;let id = 0 ;function createReactiveEffect (fn: Function , options ): ( ) => void { const effect = function reactiveEffect ( ) { if (!effectStack.includes(effect)) { try { effectStack.push(effect); activeEffect = effect; return fn(); } finally { effectStack.pop(); activeEffect = effectStack[effectStack.length - 1 ] } } } effect.id = id++; effect.options = options; return effect; }
然后是track
函数,effect
只会把用户传入的函数设置在effectStack
和activeEffect
上,而track
函数可以把activeEffect
上的函数与proxy对象
建立联系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const targetMap = new WeakMap ();export function track (target, key ) { if (activeEffect == undefined ) { return ; } let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map ())); } let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = new Set ())); } if (!dep.has(activeEffect)) { dep.add(activeEffect); } }
trigger
函数用于触发proxy对象
上的依赖函数
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 const run = (effects ) => { if (effects) effects.forEach(effect => { if (effect.options.scheduler) { effect.options.scheduler(effect); } else { effect(); } }); } export function trigger (target, type , key, value? ) { const depsMap = targetMap.get(target); if (!depsMap) { return ; } if (key == 'length' && isArray(target)) { depsMap.forEach((dep, key ) => { if (key == 'length' || key >= value) { run(dep) } }); } else { if (key !== undefined ) { let effects = depsMap.get(key); run(effects) } switch (type ) { case 'add' : if (isArray(target)) { if (parseInt (key) == key) { run(depsMap.get('length' )); } } break ; default : break ; } } }
情况1和情况2对应的代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const state = reactive([1 ,2 ,3 ]);effect(() => { app.innerHTML = ` ${state[2 ]} ` }); setTimeout (() => { state.length = 1 ; }, 1000 ) const state = reactive([1 ,2 ,3 ]);effect(() => { app.innerHTML = ` ${state} ` }); setTimeout (() => { state[10 ] = 1 ; }, 1000 )
计算属性的实现 实现和Vue2
大致相同,Vue3
把计算属性当成一个依赖,然后在渲染时收集到计算属性的effect
,在计算属性的值更新时就会触发对应的effect
另外计算属性本身也依赖一些属性,这些属性会在计算值的时候进行收集
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 import {effect, track, trigger} from "./effect" ;import {isFunction} from "../shared" ;class ComputedRefImpl { public effect; public __v_isReadonly = true ; public readonly __v_isRef = true ; public _dirty = true ; private _value; public setter; constructor (getter, setter ) { this .setter = setter; this .effect = effect(getter, { lazy: true , scheduler: () => { this ._dirty = true ; trigger(this , 'set' , 'value' ); } }); } get value () { if (this ._dirty) { this ._value = this .effect(); track(this , 'value' ); this ._dirty = false ; } return this ._value } set value (newValue ) { this .setter(newValue) } } export function computed (getterOrOptions ) { let getter; let setter; if (isFunction(getterOrOptions)) { getter = getterOrOptions; setter = () => { console .log('computed not set value' ) } } else { getter = getterOrOptions.get; setter = getterOrOptions.set; } return new ComputedRefImpl(getter, setter); }
ref实现 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 import {reactive} from "./reactive" ;import {hasChange, isArray, isObject} from "../shared" ;import {track, trigger} from "./effect" ;const convert = (val ) => isObject(val) ? reactive(val) : valclass RefImpl { public _rawValue; public readonly __v_isRef = true ; public _value; constructor (public rawValue ) { this ._rawValue = rawValue; this ._value = convert(rawValue) } get value () { track(this , 'value' ); return this ._value; } set value (newValue ) { if (hasChange(newValue, this ._rawValue)) { this ._rawValue = newValue this ._value = convert(newValue); trigger(this , 'set' , 'value' ); } } } export function ref (rawValue ) { return new RefImpl(rawValue) } class ObjectRefImpl { constructor (public _object, public _key ) { } get value () { return this ._object[this ._key]; } set value (newValue ) { this ._object[this ._key] = newValue } } export function toRefs (object ) { const result = isArray(object ) ? new Array (object .length) : {} for (let key in object ) { result[key] = new ObjectRefImpl(object , key); } return result }
详细的注释过两天再写了orz
后记 还没写完)有空再补