reactive

102 阅读7分钟

变化

  • vue3 monorepo 模块拆分

  • tree-shaking

  • vue3: proxy vue2: defineProperty

  • Vue3: 对模板编译进行了优化, 编译时生成了 Block Tree, 可以对子节点的动态节点进行收集, 可以减少比较,并且采用 patchFlag 标记动态节点

  • vue3 采用 compositionApi 进行组织功能, 解决反复横跳, 优化服用逻辑,类型推断更加方便

  • 增加 Fragment, Teleport, Suspense 组件

reactivity

  • reactive
  • shallowReactive
  • readonly
  • shallowReadonly

// 是不是仅读,是不是深度, 柯里化, new Proxy() 最核心的需要拦截数据的读取和数据的修改

function createReactiveObject(target, isReadonly, baseHandlers) {
    // 如果目标不是对象,没法拦截, reactive 只能拦截对象
    
    // 如果某个对象已经被代理了, 就不要再次代理了。 可能一个对象, 被代理是深度 又被仅读代理了
    // 有两个缓存 reactiveMap   readonlyMap
}

拦截 getter 触发收集依赖。逻辑在 baseHandlers

// 是不是仅读的, 是不是深度的

function createGetter(isReadonly = false, shallow = false) {
    return function get(target, key, receiver) {
        //1. 特殊属性的访问 __v_raw __v_isReadonly
        //2. 是不是访问的数组的几个特殊方法。两类
        //3. 是不是内置 Symbol 和 原型链上的属性
        //4. 是不是 ref
        const res = Reflect.get(target, key, receiver)
        
        if(!isReadonly) {
            // 收集依赖,等会数据变化后,更新对应的试图
            track(target,TrackOpType key)
        }
        
        if(shallow) {
            return res
        }
        
        if(isObject(res)) {// vue2 是一上来就递归, vue3 是当取值时,才去代理
            return isReadonly ? readonly(res): reactive(res)
        }
        
        return res
    }
}

effect 和 依赖收集

effect(() => { // 默认执行时会进行取值操作, 只要取值就会调用 get 方法,我们可以将对应的 effect 函数存放起来
    app.innerHTML = state.name + state.age
    
})


export function effect(fn, options) {
    const effect = createReactiveEffect(fn, options)
    
    if(!opions.lazy) {
        effect()
    }
    return effect;
}
let uid = 0;
let activeEffect; // 存储当前的 effect
const effectStack = [];
function createReactiveEffect(fn, options) {
    const effect = function reactiveEffect() {
       if(effectStack.includes(effect)) {
           try{
            effectStack.push(effect);
            activeEffect = effect;
            return fn(); // 函数执行时会取值, 会走get 方法
          }finally{
            // fn 执行完,effect 出栈
            effectStack.pop(); // 
            // 重新设置当前 effect 
            activeEffect = effectStack[effectStack.length - 1]
         }
       }
    }
    effect.id = uid++; // 
    effect._isEffect = true; // 标识是 effect 函数
    effect.raw = fn; // 保留 effect 对应的原函数
    effect.options = options;// 在 effect 上保存用户的属性
    
    return effect
}

// 问题 1
effect(() => { // effect1
    state.name ==> effect1
    effect(() => { // effect2
        state.age ==> effect2
    })
    state.hobby ==> effect1
})

// 问题2

effect(() => {
    state.age++ // age++ 会执行 effect, effect 执行会触发 state.age++ 为防止这种死循环,只有 effect 不在 effectStack 中的时候才入栈并执行
})

track

const targetMap = new WeakMap();
export function track(target, type, key) {
    // 某个对象 =》 某个属性 =》 某个effect
    if(activeEffect === undefined) {return};
    let depsMap = targetMap.get(target);
    if(!depsMap) {
        targetMap.set(target, depsMap = new Map())
    }
    let deps = depsMap.get(key);
    if(!deps) {
        depsMap.set(key, deps = new Set())
    }
    if(!deps.has(activeEffect)) {
        deps.add(activeEffect) ;// 不用 Set, activeEffect 会有重复吗
    }
}

// 问题3

effect(() => { state.name + state.name // 如果 dep 不用 Set 的话, effect 会收集多次 })

effect 特殊处理

  • effect 后返回的函数,再次被effect 是一个新的函数。
const onEffect = () => {};
const effect1 = effect(onEffect);
const effect2 = effect(effect1);
effect1 === effect2 // false

因为会判断是否时 __v_effect, 然后取出原函数后,再次重新 createEffect
  • 每次重新执行 effect 都会取值, 那么都会调用get 方法,重新收集依赖
effect.deps = [name, age] 
cleanup(); effect.deps.length = 0;
effect.deps 就是为了清除收集的。
const state = reactive({name: 'ming', age: 1})
effect(() => {
    if(state.name === 'ming') {
        console.log(state.age)
    }
})
state.age = 100
state.name = 'hong' // 后面就不收集 age 了
state.age = 200
  • 如果 effect 的选项中有 scheduler ,则会去执行 sheduler
effect 函数如果有额外的函数会优先执行顺序为: effect.options.onTriggler() --> effect.options.sheduler() --> effect()
effect(() => {
    state.name
}, {
    scheduler(effect) {
        console.log('更新')
    }
})

拦截 setter 触发更新

function createSetter(shallow = false) {
    return function set(target, key, value, receiver) {
        // 获取老值
        const oldValue = target[key]; // 
        //1. 设置的值是 响应式的或者 ref 的
        // const proxy = reactive({name: 'ming', age: ref(11)})
        // proxy.name = reactive({c: 'cc'}) 
        
        if(!shallow) {
            value = toRaw(value); // 如果设置的值是 reactive 过的,会被转化成普通对象
            if(!isArray(target) && isRef(oldValue) && !isRef(value)) {
                oldValue.value = value; // 老值是 ref ,新值不是 ref
                return true
            }
        }
        // 通过判断修改的这个 key 是否存在,来判断是走 新增逻辑还是走更新逻辑
        const hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key);
        
        if(!hadKey) {
            // 新增
            trigger(target, TriggerOpTypes.ADD, key, value)
        }else if(hasChanged(oldValue, value)) {// 更新时判断新老值是否有变化, 有变化才触发更新
        // 修改
        
            trigger(target, TriggerOpTypes.SET, key, value, oldValue)
            
        }
        
    }
}


// 找属性对应的 effect, 让他执行
export function trigger(target, type, key, newValue, oldValue) {
    // 如果这个属性没有收集过 effect, 那就不需要做任何操作
    const depsMap =  targetMap.get(target);
    if(!depsMap) { return }
    
    const effects = new Set(); // 保存要更新的 effect
    // 将所有的要执行的 effect 全部存到一个新的集合, 最终一起执行
    const add = (effectsToAdd) => {
        if(effectsToAdd) { // effectsToAdd 就是 deps 是一个 effect Set
            effectsToAdd.forEach(effect => effects.add(effect))
        }
    }
    
    // 1. 看修改的是不是数组的长度, 因为修改长度影响较大
    if(key === 'length' && isArray(target)) {
        // 如果对应的长度有依赖收集需要更新,
        depsMap.forEach((deps, depkey) => {
            if(depkey === 'length' || depkey > newValue ) { // 如果更改的长度小于收集的索引, 那么这个索引也需要触发 effect 重新执行
                // state.arr[2] 收集的索引是 2
                // state.arr.length = 1 // 更改的长度是 1 
                add(deps)  // 一个 effect 中 state.arr[1] 收集, state.arr[2] 又收集一次
            }
        })
    }else {
        // 可能是对象
        if(key !== undefined) {
            add(depsMap.get(key))
        }
        // 如果改的是数组中的索引
        switch(type) {
            case TriggerOpTypes.ADD: 
                if(isArray(target) && isIntegerKey(key)) {
                // 感觉有点问题, 如果收集的是索引 1 , 但是改的是索引 100,压根没有收集 length,
                    add(depsMap.get('length'))
                }
        }
    }
    
    effects.forEach((effect) => effect())
}

ref 、 shallowRef

ref 和 reactive 的区别 reactive 内部是 Proxy 实现, ref 是 defineProperty 实现的

function createRef(rawValue, shallow = false) {
    return new RefImpl(rawValue, shallow)
}

const convert = val => isObject(val) ? reactive(val) : val;
class RefImpl {
    public _value; // 声明一个属性, 但没有赋值
    public __v_isRef = true; // 产生的实例会被添加 __v_isRef 表示是 ref 属性
    constructor(public rawValue, public shallow) {
        // 
        this._value = this.shallow ? rawValue : convert(rawValue);
    }
    // 类的属性访问器
    get value() { // 这个 value 是 key
        track(this,TrackOpTypes.GET, 'value');
        return this._value;
    }
    set value(newValue) { // 这个 value 是 key
        if(hasChanged(newValue, this.rawValue)) {
            this._value = newValue;
            this.rawValue = this.shallow ? newValue : convert(newValue);
            trigger(this, TriggerOpTypes.SET, 'value', newValue)
        }
        
    }
}

toRef, toRefs

export function toRef(target, key) {
    return new ObjectRefImpl(target, key)
}

// ObjectRefImpl 类就是代理一下
class ObjectRefImpl {
    public __v_isRef = true;
    constructor(public target, public key) {}
    get value() {
        return this.target[this.key]
    }
    set value(newValue) {
        this.target[this.key] = newValue
    }
}

export function toRefs(target){
    const ret = isArray(target) ? new Array(target.length) : {};
    for(let key in target) {
        ret[key] = toRef(target, key)
    }
    return ret
}

const state = reactive({name: 'ming}); const r1 = toRef(state, 'name')

r1.name.value 等价于 state.name

Reflect

  • Reflect.ownKeys(obj) // 对象上所有的 key,包括 Symbol 类型的 key
  • Reflect.get
  • Reflect.set
  • Reflect.apply(fn, null, [1,2]) // Function.prototype.apply.call(fn, null, [1,2])

Symbol

  • 独一无二的值
const a1 = Symbol('a');
const a2 = Symbol('a');
console.log(a1 === a2);// false
const s1 = Symbol.for('kk'); // 声明全新的
const s2 = Symbol.for('kk')// 把之前声明的拿过来用
console.log(s1 === s2); // true
  • 元编程能力,可以改写语法本身
const obj1 = {
    [Symbol.toStringTag]: 'Hello'
}

Object.prototype.toString.call(obj1); // [object Hello]


// 隐式类型转换
const obj2 = {
    [Symbol.toPrimitive](type) {
        return '自定义值' 
    }
}
// 修改 instanceOf
const instance = {
    [Symbol.hasInstance]() {
        return '自定义值'
    }
}

copy

  • 浅拷贝 ... 展开符 和 Object.assign 一样都是处理对象的第一层

  • 深拷贝


// 通过 weakMap 控制循环引用,记录拷贝前和拷贝后的对应关系, 拷贝过的对象, 不需要再次拷贝
function deepColone(obj, hash = new WeakMap) {
    if(obj == null) return obj; // null undefined
    if(obj instanceof RegExp) {return new RegExp(obj)}
    if(obj instanceof Date) {return new Date(obj)}
    //...
    if(typeof obj !== 'objecct) {return obj}
    if(hash.has(obj)) {
        return hash.get(obj)
    }
    const copy = new obj.constructor();
    hash.set(obj, copy); // 这一行不能放到 for 循环后,因为如果有循环引用的话,就走不到后面了
    for(let key in obj) {
        if(obj.hasOwnProperty(key)) {
            copy[key] = deepClone(obj[key],hash)
        }
    }
    
    return copy;
}

compose

function sum(a,b) {return a+ b}
function len(str) {return str.length}
function prefix(str) {return '$'+ str}
const finnal = compose(prefix, len,sum)
const r = finnal('a','b')

function compose(...fns) {
    return function(...args) {
        const pre = fns.pop()
        return fns.reduceRight((r, next) => {
            return next(r)
        }, pre(...args))
    }
}

function compose(...fns) { 
    return fns.reduceRight((pre, next) => {
        return function(...args) {
            return  next(pre(...args)) // len(sum(...ars))
        }
    })  
}

reduce 也一样

被 readonly 代理后不能被 reactive 代理了 被 reactive 代理后可以被 readonly 代理 被 reactive 代理后, 不能重复被 reactive 代理了, 直接返回了 带有 __v_skip 属性的不代理

toRaw  
function toRaw(proxy){
    return (proxy && toRaw(proxy.__v_raw)) || proxy
}
markRaw

functopn markRaw(obj) {
    def(obj, '__v_skip', true)
}

数组的额外处理

// ['a', 'b', 'c'].includes(x); //

const arrayInstrumentation = {}
['includes', 'indexOf', 'lastIndexOf'].forEach(key => {
    const method = Array.prototype[key];
    arrayInstrumentation[key] = function(this, ...args) {
    // reactive([1,2]).includes('1') // 被代理后的数组
        const arr = toRaw(this)
        for(let i = 0, l = this.length; i< l; i++) {
            track(arr, TrackOpTypes.GET, i + '')
        }
        const res = method.apply(arr, args)
        if(res === -1 || res === false) {
            return method.apply(arr, args.map(toRaw));// ['a'].includes(ref('a')) // true
        }else {
            return res
        }
    } 
    
})

watchEffect(() => {
    arr.push(1)
})
watchEffect(() => {
    arr.push(2)
})
如果不特殊处理会无限循环

['push','pop', 'shift','unshift','splice'].forEach(key => {

    const method = Array.prototype[key];
    arrInstrumentations[key] = function(this, ...args) {
        pauseTracking();
        const res = method.apply(this, args)
        resetTracking();
        return res;
    }
})

内置 Symbol 或者是原型链上查找的也直接返回

if(isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
    return res;
}

解包

const r = reactive({a: ref(2)})
r.a   === 2 // 此时不需要有 value; // 会解包
const arr = [ref(2), 3, 4]
arr[0].value === 2 // 此时需要带有 value
if(isRef) {
// 对于数组取值不解包
   const shouldUnwrap = !targetIsArray || !isIntegerKey(key);
   return shouldUnwrap ? res.value : res;
}

流程梳理

  • reactive
    • 判断是不是对象
    • 重复代理的情况
    • 对不同类型进行 proxy
    • 做缓存
  • get baseHandler
    • 特殊属性处理 __v_raw __v_isReadonly
    • 是否是数组的特殊方法
    • Symbol 内置属性和原型链属性不处理
    • 对 ref 进行处理,对象的属性值是 ref 会解包,数组的元素是 ref 不会解包
  • set baseHandler
    • 设置值的时候对 ref 单独处理,target 不是数组, 老值是 ref ,新值不是 ref, oldValue.value = value
    • 对新增和修改做不同的处理
  • track 就是维护属性和 effect 之间的关系 {target: {name: [effect, effecct], age: [effect]}}
  • trigger 就是找到 target 中对应的属性的 effect 列表,然后执行 effect
  • 响应式原理: 属性会收集 effect, 属性更新会让收集的 effect 重新执行

        ------ @vue/compiler-sfc
       |       |                        |
vue ----      @vue/compiler-dom --->   @vue/compiler-core
       |
       |__ @vue/runtime-dom --> @vue/runtime-core --> @vue/reactivity

@vue/runtime-core 是和平台无关的