vu3-04 Reactivity API 编写

136 阅读7分钟

在包中追加依赖,同样是改workspace

也可以用命令来搞

//@vue/reactivity包添加依赖是@vue/share@1.0.0
yarn workspace @vue/reactivity add @vue/share@1.0.0 
// @vue/reactivity 依赖于 @vue/share 
// @vue/share  就会加到  @vue/reactivity 它的package.json里面

reactivity api 如何实现的?

vue3 的源代码不能用npm 去安装依赖、必须用yarn安装。

vue3 npm run dev 默认是去 packages/vue 下面去打包。

vue3 npm run build 是打包packages/下面所有的内容。

npm run dev // 执行完了这个打包完了去到dev example/新建index.html 做测试。
// index.html
<script src="../dist/vue.global.js"></script> // 打包的文件名字暴露出来的就是Vue

// Vue的几个API介绍

import {reactive,shallowReactive,readonly, shallowReadonly} from "Vue";

// reactive对象响应式封装 

// 只管第一层对象响应shallowReact

// readonly 对象不能修改属性不能改

// shallowReadonly 只有第一层只读


// reactive
let state = reactive({a:1, b:{c:1}});
// a b c 都是响应式的。
// 返回的是Proxy


// shallowReactive
// let state = shallowReactive({a:1, b:{c:1}});
// 只有 a,b是响应式的 c 就不是了只有第一层才会被代理。

// readonly
let state = readonly({a:1, b:{c:1}});
// a b c 还是proxy 区别在于你不能修改这个属性值。 state.a = 1 是不行的

let state = shallowReactive({a:1, b:{c:1}});

// a b  是proxy 只有第一层的是只读的 state.b.c 是可以修改 c 没有被代理


// 为什么搞一个只读的属性?
// 为了效率,如果仅仅是个只读的就不需要收集依赖改变它了。因为改不了不会除法视图的更新。 如果是只读的收集依赖的时候就不需要理财只读的

实现 reactive 这几个API 回到 package/reactivity

package/reactivity/src/index.ts 整合内容加导出

package/reactivity/src/core/reactive api 实现

工具share 包 工具类共享的

在开始之前需要分析这几个API 到底搞了些什么事情

1、首先来说核心就是代理。 2、每个API实现的代理方式有不同。 3、拦截器的方式不同。

package/reactivity/src/index.ts

import {
    reactive,             // 全部响应式
    shallowReactive,      // 单层的响应式
    readonly,             // 只读
    shallowReadonly,      // 单层只读

} from "./core/reactive";


export {
    reactive,
    shallowReactive,
    readonly,
    shallowReadonly,
}

package/reactivity/src/core/reactive.ts

import {
    mutableHander,
    shallowMutableHander,
    readonlyHandlers,
    shallowReadonlyHandlers,
} from "./basehander";

/**
 * reactive api
 * 
 * 这一组方法
 * 区别是不是只读
 * 是不是只有一层(深度)
 * 
 * 科里化
 * 外层接受一些公共的方参数里面来区分
 * 
 * "sourceMap": true,  开启可以源代码调试 ts的配置
 */

// 代理全部的内容
// @params target 被代理的对象
function reactive(target={}){
    return createProxy(target, false, mutableHander); 
}

// 只被代理一层其余的不代理
function shallowReactive(target={}) {
    return createProxy(target, false, shallowMutableHander); 
}

// 对代理的属性进行设置报错
function readonly(target={}) {
    return createProxy(target, true, readonlyHandlers); 
}

// 就对代理的第一层进行只读
function shallowReadonly(target={}) {
    return createProxy(target, true, shallowReadonlyHandlers); 
}

// 分析一下他们都需要进行对对象进行代理
// 只是分只读还是不是只读的在就是拦截器的不同
// 所以公共创建代理就产生了
// 在这之前说说科里化函数
// 函数柯里化是一种将接受多个参数的函数转换为接受一个参数并返回一个新函数的技术。柯里化后的函数可以方便地进行函数组合、部分应用和延迟执行,从而提高代码的可读性、可维护性和可重用性。以下是函数柯里化的一些优势:
// 简化参数传递:柯里化后的函数可以接受一个参数,而不是多个参数。这样可以简化函数调用时的参数传递,使代码更加清晰易懂。
// 支持部分应用:柯里化后的函数可以返回一个新函数,这个新函数可以只接受一部分参数。这样就可以支持部分应用,即先传入部分参数,然后再传入剩余参数。这样可以提高代码的可重用性和灵活性。
// 支持函数组合:柯里化后的函数可以方便地进行函数组合,即将多个函数组合成一个函数。这样可以提高代码的可读性和可维护性。
// 支持延迟执行:柯里化后的函数可以返回一个新函数,这个新函数可以等到所有参数都准备好之后再执行。这样可以支持延迟执行,即在需要的时候才执行函数。
// 更好的可测试性:柯里化后的函数可以更方便地进行单元测试,因为可以更容易地构造和传入测试数据,并对测试数据进行断言和验证。

// createProxy

 import { isObject } from "@vue/share";
 // WeakMap 自动垃圾回收,不会有内存泄漏的问题
 // 用来存储响应式的
 const ReactivityMap = new WeakMap();
 // 用来存储readonly的
 const ReadonlyMap = new WeakMap();

/**
 * 拦截数据的读取和写入
 
 * @param obj 代理对象
 * @param isReadOnly 是否只读
 * @param mutableHander 拦截器
 */
 
 function createProxy(obj = {}, isReadOnly = false, baseMutableHander = {} ) {
     /**
     * 最核心的也就是创建了个proxy
     * 如果目标不是对象就直接返回!!!
     * 这个API只能来拦截对象
     * ref可以是普通类型但是!!其实内部也是个对象为啥用.value 内部创建了个对象
     * 在share里面写一下公共的判断方法
     * 采用了Monorepo 这种管理方式 如果引用库本身的内容比如输入个 isObject 就会自动的加载进来这个包 这个是Ts实现的
     * 
     * pnpm https://juejin.cn/post/7124142007659790372
     */
     
     // 如果被代理的是个非对象直接返回不做任何的处理
     if(!isObject(obj)) {
         return obj; // 给的什么就返回什么原样的放返回
     }
     
     // 如果某个对象已经被代理了就别再代理了,加了一层缓存机制。
     // 创建一个仅读的空间
     // 创建一个响应式的空间
     // 根据 isReadOnly 来判断到底要缓存到什么位置
     
     // 根据判断来解锁最后用的什么缓存
     let cacheMap = null;
     
     if(isReadOnly) {
         // 只读的缓存存储
        // ReactivityMap
        cacheMap = ReadonlyMap;
     } else {
        // 非只读的缓存存储
        // ReadonlyMap
        cacheMap = ReactivityMap;
     }
    
     // 返回的代理对象
     let PROXY = null;
     
     if(!cacheMap.has(obj)){ // 防止一个对象被反复的代理
        // 开始代理这个对象
        PROXY = new Proxy(obj, baseMutableHander);

        // 开始缓存 -- obj 这个对象已经被代理。
        cacheMap.set(obj, PROXY);
    } else {
        PROXY = cacheMap.get(obj);
    }
    
    return PROXY;
     
 }
// package/share/src/index.ts
/**
 * 
 * @param target 目标对象
 * @returns 是否是一个对象
 */
function isObject(target:any):boolean {
    return typeof target === 'object' && null !== target;
}


/**
 * 
 * @param sourceObj 哪个对象
 * @param key       对象的什么属性
 * @param msg       错误消息
 */
const Exception = (sourceObj:any, key:any, msg:string = '只读的不能设置') => {
    console.warn(`${key}:${msg}`)
}

export {
    isObject,
    Exception,
}

开始处理拦截器相关的内容这个是个核心有些概念需要理解下。

package/reactivity/src/core/basehander.ts

import { Exception, isObject } from "@vue/share";
import { reactive, readonly } from "./reactive";

/**
 * Reflect
 * 为什么用 Reflect 这个?
 * 
 * 后续Object上的方法会被迁移到 Reflect 这上面来。
 * 比如
 * Object.getPropertyof() 
 * 以前
 * target[key] = value设置值可能会失败。而且没有异常也没有返回值
 * Reflect.set(target, key, value, reactiver); 这个一定会成功而且会有返回值。 proxy 不一定非要和 Reflect 配套只是说一般来说。
 * Reflect 这东西可以单独使用不需要非捆绑使用
 * reactiver 这东西一般用不到。
 */
 

/**
 * 统一的来处理get
 * 核心的获取
 * 利用科里化函数来实现 分布到集中。
 */
const Getter = (isReadOnly:boolean=false, isShallow:boolean=false) => {
    return function(target:any, key:any, reactiver:any) { // 通过proxy 取值的时候会触发get reactiver 代理对象的本身
        // Reflect 一般和 Proxy联用
        // 从reactiver代理对象反射到原生对象获取key 你去proxy取值我就吧原来的 target 的值给你
        let r = Reflect.get(target, key, reactiver);

        if(!isReadOnly){
            // 收集依赖 如果不是只读的需要收集起来一会更新试图,如果是只读的就不牵扯到视图的更新。


        }

        // 如果就一层代理就不需要走下面递归了,为了效率采用惰性的嵌套递归代理。
        if(isShallow) {
            // 如果是一层的就直接返回结果
            // 
            return r;
        }

        // console.log("key:::", key, isObject(r), target);
        
        if(isObject(r)) {
            // 如果不是一层的就要考虑递归一下!
            // vue2 上来就做代理 vue3取值的时候会进行代理 vue3的代理模式 获取值的时候一层层的进行代理 最后到不是对象为止 懒代理
            return isReadOnly ? readonly(target) : reactive(r);
            
        }


        return r;
    }
}


/** 
 * 设置
 * 核心设置
 * @param isShallow 
 */
const Setter = (isShallow:boolean = false) => {
    return function(target:any, key:any, value:any, reactiver:any) { // 通过proxy设置值的时候会触发get
      return Reflect.set(target, key, value, reactiver); // target[key] = value;
    }
}



/**
 * 拦截器 全部的属性 reactive
 * 深度的
 */
const mutableHander = {
    get: Getter(false, false),
    set: Setter(false)
};

/**
 * shallowMutableHander
 * 非深度的
 */
const shallowMutableHander = {
    get: Getter(false, true),
    set: Setter(true),
};
/**
 * readonlyHandlers
 * 仅读的时候set应该抛出异常
 * 深度的
 */
const readonlyHandlers = {
    get: Getter(true, false),
    set(target:any, key:any) {
        Exception(target, key);
    }
};
/**
 * shallowReadonlyHandlers
 * 仅读的时候set应该抛出异常
 * 非深度
 */
const shallowReadonlyHandlers = {
    get: Getter(true, true),
    set(target:any, key:any) {
        Exception(target, key);
    }
}


export {
    mutableHander,
    shallowMutableHander,
    readonlyHandlers,
    shallowReadonlyHandlers,
}