Vue3响应式API-Reactive手写实现

Jiafeng

分类: Vue 349 0

前言

  1. 搭建本地开发环境,这里推荐使用pnpm包管理工具官方文档

  2. 安装相关依赖 esbuild minimist typescript

    esbuild是一个类似webpack构建工具。它的构建速度是 webpack 的几十倍。官方文档

    minimist用来解析命令行参数 文档

    pnpm init
    pnpm install esbuild minimist typescript
  3. esbuild配置

    // minimist用来解析命令行参数
    const args = require('minimist')(process.argv.slice(2)); // node scripts/dev.js reactivity -f global
    const target = args._[0] || 'reactivity';
    const format = args.f || 'global';
    
    const { build } = require('esbuild');
    const { resolve } = require('path');
    
    // 开发环境只打包一个
    const pkg = require(resolve(__dirname, ../packages/${target}/package.json));
    
    // iife 立即执行函数 (function(){})()
    // cjs node模块 module.exports
    // esm 浏览器中的esModule模块 import
    const outputFormat = format.startsWith('global') ? 'iife' : format === ' cjs' ? 'cjs' : 'esm';
    
    const outfile = resolve(__dirname, ../packages/${target}/dist/${target}.${format}.js);
    
    build({
       entryPoints: [resolve(__dirname, ../packages/${target}/src/index.ts)],
       outfile,
       bundle: true,
       sourcemap: true,
       format: outputFormat,
       globalName: pkg.buildOptions?.name,
       platform: format === 'cjs' ? 'node' : 'browser',
       watch: {
           onRebuild(error) {
               if (!error) console.log(result---)
           }
       }
    }).then(() => {
       console.log(watching---);
    })

功能介绍

  • 1s后age+1,页面重新渲染

实现方法

  1. 创建effect函数,入参为要实现响应式的依赖语句,默认数据变化执行更新,先将正在执行的effect作为全局变量,渲染取值
  2. reactive创建一个响应式对象 new Proxy,在get方法中通过track跟踪收集依赖(这里用WeakMap数据结构去存储多个需要响应式的object的depsMap
  3. 稍后数据发生变化,在set中调用trigger,通过对象属性查找对应的effect集合,找到effect全部执行
  • WeakMap结构如下(对象: map(属性: set(effect)))

以下为reactive实现,源码请点击这里

// effect.ts
export let activeEffect: any = undefined;

function cleanupEffect(effect: any) {
    const { deps } = effect; // deps里面装的是name对应的effect
    for (let i = 0; i < deps.lenght; i++) {
        deps[i].delete(effect); // 解除effect,重新依赖收集
    }
    effect.deps.length = 0;
}
export class ReactiveEffect {
    public parent = null;
    public deps = [];
    // 这里表示在实例上新增了active属性
    public active = true; // effect默认激活状态

    constructor(public fn: any, public scheduler: any) { } // 用户传递的参数也会当作this上 this.fn = fn
    // 执行effect
    run() {
        // 这里表示如果是非激活的情况,只需要执行函数,不需要进行依赖收集
        if (!this.active) { return this.fn() };
        // 收集依赖 核心就是将当前的effcet 和 稍后渲染的属性关联在一起
        try {
            this.parent = activeEffect
            activeEffect = this;
            // 这里我们在执行用户函数前将之前收集的内容清空
            cleanupEffect(this);
            // 当稍后调用取值操作的时候就可以获取到这个全局的activeEffect了
            return this.fn();
        } finally {
            activeEffect = this.parent;
        }
    }
    stop() {
        if (this.active) {
            this.active = false;
            cleanupEffect(this); // 停止effect收集
        }
    }
}

export function effect(fn: any, options: any = {}) {
    // 这里fn可以根据状态变化 重新执行 effect可以嵌套写
    const _effect = new ReactiveEffect(fn, options?.scheduler);// 创建响应式的effect
    _effect.run(); // 默认先执行一次
    const runner: any = _effect.run.bind(_effect); // 绑定this执行
    runner.effect = _effect; // 将effect挂载到runner函数上
    return runner
}

// 一个effect对应多个属性,一个属性对应多个effect
const targetMap = new WeakMap();

export function track(target: any, type: any, key: any) {
    if (!activeEffect) 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()))
    }
    trackEffect(dep)
}

export function trackEffect(dep: any) {
    if (activeEffect) {
        // 去重
        let shouldTrack = !dep.has(activeEffect);
        if (shouldTrack) {
            dep.add(activeEffect);
            // 存放的是属性对应的set,让effect记录对应的dep,清理用到
            activeEffect.deps.push(dep);
        }
    }
}

// 对象 某个属性->多个effect
// WeakMap = {对象:Map{name:Set->effect}}
// {对象:{name: []}}

export function trigger(target: object, type: any, key: any, value: any, oldValue: any) {
    console.log(targetMap);

    const depsMap = targetMap.get(target);
    if (!depsMap) return // 触发的值不在模板中使用

    let effects = depsMap.get(key); //找到属性对应的effect
    if (effects) {
        tiggerEffect(effects)
    }
}

export function tiggerEffect(effects: any) {
    effects = [...effects]

    effects.forEach((effect: any) => {
        // 我们在执行effect的时候 又要执行自己,需要屏蔽 避免无限调用
        if (effect !== activeEffect) {
            if (effect.scheduler) {
                effect.scheduler(); // 如果传入调度函数,调用调度
            } else {
                effect.run(); // 否则默认刷新视图
            }
        }
    })
}
// reactive.ts
import { isObject } from "@vue/shared";
import {mutableHandles, ReactiveFlags} from "./baseHandle"

const reactiveMap = new WeakMap(); // key只能是对象

// 将数据转换为响应式数据,只能做对象的代理
// 实现同一个对象代理多次,返回同一个代理
// 代理对象再次被代理 可以直接返回
export function reactive(target: any){
    if(!isObject(target)) return
    let exisitingProxy = reactiveMap.get(target);
    if(target[ReactiveFlags.IS_REACTIVE]) return target
    if(exisitingProxy) return exisitingProxy

    // 并没有重新定义属性,只是代理,在取值的时候调用get,赋值时调用set
    const proxy:any = new Proxy(target, mutableHandles)
    reactiveMap.set(target, proxy)
    return proxy
}

export function isReactive(value: any){
    return !!(value && value[ReactiveFlags.IS_REACTIVE])
}
// baseHandle.ts
import { isObject } from "@vue/shared"
import { track, trigger } from "./effect"
import { reactive } from "./reactive"
export const enum ReactiveFlags {
    IS_REACTIVE = '__v_isReactive'
}

export const mutableHandles = {
    /**
     * 
     * @param target 需要取值的目标对象
     * @param key 需要获取的值的键值
     * @param receiver 如果target对象中指定了getter,receiver则为getter调用时的this值。
     * @returns 
     */
    get(target: object, key: PropertyKey, receiver: any) {
        if (key === ReactiveFlags.IS_REACTIVE) return true

        track(target, 'get', key)
        let res = Reflect.get(target, key, receiver)
        if (isObject(res)) {
            return reactive(res) // 深度代理实现
        }
        return res
    },
    /**
     * 
     * @param target 设置属性的目标对象。
     * @param key 设置的属性的名称。
     * @param value 设置的值。
     * @param receiver 如果遇到 setter,receiver则为setter调用时的this值。
     * @returns 
     */
    set(target: any, key: PropertyKey, value: any, receiver: any) {
        let oldValue = target[key];
        let result = Reflect.set(target, key, value, receiver)
        // 值变化了 进行更新
        if (oldValue !== value) {
            trigger(target, 'set', key, value, oldValue)
        }
        return result
    }
}
  • 0人 Love
  • 0人 Haha
  • 0人 Wow
  • 0人 Sad
  • 0人 Angry
Vue3、Vuejs

作者简介: Jiafeng

共 0 条评论关于 “Vue3响应式API-Reactive手写实现”

Loading...