Vue源码学习(3) -- 响应式系统(2)

Vue源码学习(3) -- 响应式系统(2)

前言

本文为Vue3源码学习第三篇。,上文学习了reactive以及简易的effect。本文主要学习readonlyisReactiveisReadonly isProxyshallowReactiveshallowReadonly等功能的实现

1. Readonly

1.1 文档详情

查看Vue3文档响应式Api可得

Type

function readonly<T extends object>(
  target: T
): DeepReadonly<UnwrapNestedRefs<T>>
复制代码

Details

readonly是深层的代理,任何被访问的属性都是只读的。他们和reactive相同,接收一个对象(响应式或者纯对象)或者ref并返回原始值的只读代理

Example

const original = reactive({ count: 0 })

const copy = readonly(original)

watchEffect(() => {
  // works for reactivity tracking
  console.log(copy.count)
})

//变更original会触发copy的监听器
original.count++

// 变更copy将会失败并导致警告
copy.count++ // warning!
复制代码

1.2、源码实现

在实现readonly之前,首先进行测试代码的编写

//readonly.spec.ts
describe("happy path readonly",()=>{
    it("should make nested values readonly",()=>{
        const original = { 
            foo: 1,
        }        
        const wrapped = readonly(original)
        
        expect(wrapped).not.toBe(original)
        expect(wrapped.foo).toBe(1)
        
    });
    it("should call console.warn when set", () => {
        console.warn = jest.fn();
        const user = readonly({
          age: 10,
        });
        
        //readonly响应式对象的property是只读的
        user.age = 11;
        
        //修改readonly响应式对象的property的值会调用console.warn发出警告 
       expect(console.warn).toHaveBeenCalled();
  });    
})
复制代码

readonlyreactive的区别在于readonly的对象是只读的。

所以在readonly的实现上与reactive的实现基本一致,区别在于是否进行依赖收集readonly是只读的,可以不进行依赖收集

代码实现


//reactive.ts
export function readonly(obj){
    //返回Porxy实例
    return new Porxy (obj,{
        //对原始对象的get进行代理
        get(target,key){
        
            const res =Reflect.get(target,key)
            
            return res
        },
        set(target,key,newVal){
            //readonly是只读的,set会报错
            console.warn(`"${String(key)}"set失败,因为${target}是readonly类型`)
            return true;
         }  
 })
复制代码

执行yarn test readonly命令运行readonly的测试,可以看到测试通过,这样就完成了readonly最基础的实现

重构的思考

reactivereadonly在代码层面的实现过程中有大量的重复代码,例如均返回Proxy对象,gettersetter方法也有重复代码。所以对重复代码进行抽离,提高代码可读性

创建baseHandlers.ts文件,用于构造Proxygettersetter,相关的代码并抽离公共代码和使用全局变量进行缓存

//baseHadnler.ts

//生成getter方法工具函数
function createGetter(isReadonly = false){
    return function(target,key){
        const res = Reflect.get(target,key)
        
        //判断是否为reactive/readonly,reactive进行依赖收集,readonly不需要收集
        if(!isReadonly){
            //track(target,key)
        }
        
        return res
}

//生成setter方法工具函数
function createSetter(){
    return function (target,key,newVal){
    
        const res = Reflect.set(target,key,newVal)
        
        //触发依赖
        trigger(target,key)
        return res
 }
 
 //reactive
 const reactiveGet = createGetter()
 const reactiveSet = createSetter()
 
 export const reactiveHandlers = {
     get:reactiveGet,
     set:reactiveSet
 }    
 
//readonly

const readonlyGet = createGetter(true)

export const readonlyHandlers = {
    get:readonlyGet,
    set(target,key,newVal){
        console.warn(`key :"${String(key)}" set 失败,因为 target 是 readonly 类型`, target, newVal);
        
        return true;
    }
}    
复制代码

reactive.ts文件进行重构

//reactive.ts

//工具函数
function creactReactiveObject(target,mutableHandlers){
    if (!isObject(target)) {
        console.warn(`target${target}必须是一个对象`);
        return target;
      }
     return new Proxy(obj,mutabHandlers)
} 

//reactive
export function reactive(obj){
    return createReactiveObject(obj,reactiveHandlers)
}

//readonly
export function readonly(obj){
    return createReactiveObject(obj,readonlyHandlers)
}

复制代码

重构完成

2、isReactive、isReadonly、isProxy

2.1、isReactive

2.1.1、文档详情

Vue3响应式Api

Type
function isReactive(value: unknown): boolean
复制代码
Detials

检查对象是否由reactive或则shallowReactive创建的proxy代理

Example
import { reactive, isReactive } from 'vue'
export default {
  setup() {
    const state = reactive({
      name: 'John'
    })
    console.log(isReactive(state)) // -> true
  }
}
复制代码

如果代理是readonly创建的,但包裹了reactive创建的另一个代理,他也会返回true

import { reactive, isReactive, readonly } from 'vue'
export default {
  setup() {
    const state = reactive({
      name: 'John'
    })
    // 从普通对象创建的只读 proxy
    const plain = readonly({
      name: 'Mary'
    })
    console.log(isReactive(plain)) // -> false

    // 从响应式 proxy 创建的只读 proxy
    const stateCopy = readonly(state)
    console.log(isReactive(stateCopy)) // -> true
  }
}
复制代码

执行yarn test reactive命令运行reactive的测试,可以看到测试通过,这样就实现了isReactive

2.1.2、源码实现

在实现isReactive之前,首先进行测试代码的编写

// reactive.ts

describe("reactive",()=>{
    it("happy path",()=>{
    
    const original = { foo: 1 };
    const observed = reactive(original);
   
    //isReactive
    expect(isReactive(observed)).toBe(true);
    expect(isReactive(original)).toBe(false);
    
    })
})
复制代码

在设计getter的时候 传入了一个isRaedonly的参数 默认false,我们利用obj[__v_isReactive],调用getter函数,在getter函数中进行判断,如果纯如的key ===__v_isReactive,则表明是reactive返回!isReadonly

代码实现

// basehandlers.ts

function createGetter(isReadonly = false){
    return function (target,key){
    
        //当property名为__v_isReactive时,表明正在调用 isReactive,直接返回 !isReadonly
        if(key==="__v_isReactive"){
            return !isReadonly
        }
        /*其他代码*/
}        
复制代码

reactive.ts

// reactive.ts


exprot function isReactive(obj){
    
    return !! obj["__v_isReactive"]

}
复制代码

2.2、isReadonly

2.2.1 文档详情

isReadonly的效果跟isReactive类似:判断代理对象是不是readonly类型。

Vue3响应式Api

type
function isReadonly(value: unknown): boolean
复制代码

2.2.2、源码实现

测试代码

//readonly.spec.ts
describe('readonly', () => {
  it('should make values readonly', () => {
    const original = { foo: 1 }
    const wrapped = readonly(original)
    console.warn = jest.fn()
    
    expect(wrapped).not.toBe(original)
    
    // 对 readonly 响应式对象调用 isReactive 返回 false
    expect(isReactive(wrapped)).toBe(false)
    
    // 对 readonly 响应式对象调用 isReadonly 返回 true
    expect(isReadonly(wrapped)).toBe(true)
    
    
    // 对普通对象调用 isReadonly 返回 false
    expect(isReadonly(original)).toBe(false)
    
    expect(wrapped.foo).toBe(1)
    wrapped.foo = 2
    expect(wrapped.foo).toBe(1)
    expect(console.warn).toBeCalled()
  })
})
复制代码

代码实现

// basehandlers.ts

function createGetter(isReadonly = false){
    return function (target,key){
        /*其他代码*/
        
        //当property名为__v_isReadonly时,表明正在调用 isReadonly,直接返回 isReadonly
        if(key==="__v_isReadonly"){
            return isReadonly
        }
        
        /*其他代码*/
}        
复制代码
// reactive.ts

exprot function isReactive(obj){
    
    return !! obj["__v_isReadonly"]

}
复制代码

执行yarn test readonly命令运行readonly的测试,可以看到测试通过,这样就实现了isReadonly

2.3、 isProxy

2.3.1、 文档详情

Vue3响应式Api

检查对象是否是由reactivereadonly创建的 proxy。 type

function isProxy(value: unknown): boolean
复制代码

2.3.2、 源码实现

测试代码

//reactive.spec.ts
describe("reactive", () => {
  it("happy path", () => {
   const original = { foo: 1 };
    const observed = reactive(original);
   /*其他代码*/

    //isProxy
    expect(isProxy(observed)).toBe(true);
    expect(isProxy(original)).toBe(false);
  });
 }) 
复制代码
//readonly.spec.ts
describe("readonly", () => {
    it("should make nested values readonly", () => {
        /*其他代码*/
        
         //isProxy
    expect(isProxy(wrapped)).toBe(true);
    expect(isProxy(original)).toBe(false);
    
    }
 })                
})
复制代码

满足isReactive和isReadonly任意一个就是proxy

代码实现

//reactive.ts

export function isProxy(obj){

    return isReactive(obj) === true || isReadonly(obj) === true
 }
复制代码

分别执行yarn test reactiveyarn test readonly命令运行reactivereadonly的测试,可以看到测试均通过,这样就实现了isProxy

2.4、 代码重构

isReactiveisReadonly的实现中使用到的特殊 property 的名为字符串,需要对其进行优化,创建并导出枚举类型ReactiveFlags用于保存这两个字符串:

//baseHandler.ts 
//用于保存 isReactive 和 isReadonly 中使用的特殊 property 的名

export cosnt enum ReactvieFlags {
    IS_REACTIVE = '__v_isReactive', 
    IS_READONLY = '__v_isReadonly'
}
function createGetter(isReadonly = false) { 
    return function (target, key) { 
        if (key === ReactiveFlags.IS_REACTIVE) { 
            return !isReadonly 
         } else if (key === ReactiveFlags.IS_READONLY) {
             return isReadonly 
         } 
         
         /* 其他代码 */ 
    } 
}
复制代码
//reactive.ts
export function isReactive(value): boolean {
  return !!value[ReactiveFlags.IS_REACTIVE]
}

export function isReadonly(value): boolean {
  return !!value[ReactiveFlags.IS_READONLY]
}
复制代码

3、 完善reactivereadonly——响应式嵌套

reactivereadonly的响应式转换是“深层”的,会影响所有嵌套的 property,即嵌套的 property 也应该是响应式的。

分别在reactive.spec.tsreadonly.spec.ts测试文件中添加代码

// reactive.spec.ts
describe("reactive", () => {
  it("nested reactive", () => {
    const original = {
      nested: {
        foo: 1,
      },
      arr: [{ bar: 2 }],
    };
    const observed = reactive(original);

   // 嵌套对象是响应式的
    expect(isReactive(observed.nested)).toBe(true)
    expect(isReactive(observed.arr)).toBe(true)
    expect(isReactive(observed.arr[0])).toBe(true)
  });
})
复制代码
//readonly.spec.ts
describe("readonly", () => {
  it("should make nested values readonly", () => {
    const original = { foo: 1, bar: { baz: 2 } };
    const wrapped = readonly(original);
    expect(wrapped).not.toBe(original);
    expect(wrapped.foo).toBe(1);

    //isReadonly
    expect(isReadonly(wrapped)).toBe(true);
    expect(isReadonly(original)).toBe(false);

    //isProxy
    expect(isProxy(wrapped)).toBe(true);
    expect(isProxy(original)).toBe(false);

    //嵌套
    expect(isReadonly(wrapped.bar)).toBe(true);
  });
})
复制代码

代码实现

//baseHandlers.ts


//工具函数,用于判读是对象或者数组
function isObject(value){
     return typeof value === "object" && res !== null
}
export function createGetter(isReadonly = false){
    return function (traget,key){
    
        /*其他代码*/
        
         const res = Reflect.get(target,key) 
        //判断res是否是对象或数组
        if(isObject(res)){
            return isReadonly ? readonly(res) :reactive(res)
        }
        
        /*其他代码*/
 }       
复制代码

分别执行yarn test reactiveyarn test readonly命令运行reactivereadonly的测试,可以看到测试均通过,这样就进一步完善了reactivereadonly的实现。

4、shallowReadonlyshallowReacitve的实现

文档描述

shallowReative

type

function shallowReactive<T extends object>(target: T): T
复制代码

detailsreactive不同,shallowReactive只追踪自身property的响应性,但不执行嵌套对象的生层次的响应式转换(暴漏原始值)

exmpale

const state = shallowReactive({
    foo:1,
    nested:{
        bar:2
    }
})

//改变state本身的property是响应式的
state.foo ++ //响应式
//但是不转换嵌套对象的响应式
isReactive(state,nested)   //false
state.nested.bar ++ //非响应式

复制代码

reactive不同,任何使用ref的 property 都不会被代理自动解包

shallowReadonly

type

function shallowReadonly<T extends object>(target: T): Readonly<T>
复制代码

detailsreadonly不同,shallowReadonly之追踪自身的 property 我i只读,但不执行嵌套对象的深度只读转换(暴露原始值)

exmaple

const state = shallowReadonly({
  foo: 1,
  nested: {
    bar: 2
  }
})

// 改变 state 本身的 property 将失败
state.foo++
// ...但适用于嵌套对象
isReadonly(state.nested) // false
state.nested.bar++ // 适用
复制代码

源码实现

shallowReactive

编写测试代码

//shallowReactive.spec.ts
describe("shallwoReactive",()=>{
    test("should not make non-reactive properties reactive",()=>{
        const state = shallowReactive({
           foo:1,
            nested:{
                bar :2
             }   
        })
        
        expect(isReactive(state)).toBe(true)
        expect(isReactive(state.nested)).toBe(false)
        
    });
    
    test("happy shallwoReactive",()=>{
        const state = shallowReactive({
            foo:1,
            nested:{
                bar:2
             }   
        })
        
        let nextState;
        effect(()=>{
            nextState = props.foo+ 1
        })
        expect(nextProps).toBe(2)
        
        state.foo ++
        expect(nextPorps.toBe(3))
    })
})
复制代码

功能实现 shallowReactivereactive区别在于对于内层嵌套对象响应式不同,shallowReactive内层不是响应式对象。

createGetter传入一个isShallow参数,为true不进行内部嵌套对象的响应式操作,为false则执行

//baseHanlders.ts

function createGetter(isReadonly=false,isShallwo = false){
    return function (target,key){
        
        /*其他代码*/
        
        const res = Reflect.get(target,key)
        
        if(!isReadonly){
            track(target,key)
        }    
        //是否为shallow 
        if(shallow){
            return res
        }
        if (isObject(res)) {
      //递归调用
      // isReadonly == true -> 表明是readonly对象 :是reactive对象
          return isReadonly ? readonly(res) : reactive(res);
        }
        
        return res
}

// shallowReactive
const shallowReactiveGet = functon createGetter(false,true)
const shallowReactiveSet = function createSetter()
export const shallowReactiveHandlers = {
  get: shallowReactiveGet,
  set,
};
复制代码
//reactive.ts

export function  shallowReactive(obj){
    return createReactiveObject(obj,shallowReactiveHandlers)
复制代码

shallowReadonly

编写测试代码

//shallowReadonly.spec.ts

describe("shallowReadonly", () => {
  test("should not make non-reactive properties reactive", () => {
    const props = shallowReadonly({ n: { foo: 1 } });
    expect(isReadonly(props)).toBe(true);
    expect(isReadonly(props.n)).toBe(false);
  });

  it("should call console.warn when set", () => {
    console.warn = jest.fn();
    const user = shallowReadonly({
      age: 10,
    });

    user.age = 11;
    expect(console.warn).toHaveBeenCalled();
  });
});
复制代码

代码实现 shallowReadonly功能的实现,其实在shallowReactive实现过程中已经实现,区别在于createGetter函数的参数不同

// baseHandlers.ts

//shallowReadonly
const shallowReadonlyGet = funtion createGettr
r(true,true)

export const shallowReadonlyHandlers = {
  get: shallowReadonlyGetter,
  set: function (target, key, newVal) {
    console.warn(`key :"${String(key)}" set 失败,因为 target 是 readonly 类型`, target, newVal);
    return true;
  },
};
复制代码
//reactive.ts

//shallowReadonly
export function shallowReadonly(obj){
    return createReactiveObject(obj,shallowReadonlyHandlers)
复制代码

分别执行yarn test shallowRreactiveyarn test shallowReadonly命令运行shallowRreactiveshallowReadonly的测试,可以看到测试均通过,这样就实现了shallowRreactiveshallowReadonly

未完待续

猜你喜欢

转载自juejin.im/post/7074847535621210126