• 手写实现Vue3 reative


    前言

    reative原理详解:https://blog.csdn.net/weixin_39503495/article/details/127058245

    环境搭建

    使用typescriptvite构建工具

    // 初始化项目
    npm init -y
    // 安装依赖
    npm i vite typescript -D
    
    • 1
    • 2
    • 3
    • 4
    1. 初始化typescript

      tsc --init

    2. 创建index.htmlindex.tsvite配置文件vite.config.ts、src目录。

    3. indext.html中引入index.ts

    index.html

    DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Documenttitle>
    head>
    <body>
      <script src="./index.ts" type="module">script>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    vite.config.ts

    import { defineConfig } from "vite";
    
    export default defineConfig({
      server: {
        port: 3000,
      }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    目录结构

    	|-- index.html
    	|-- index.ts
    	|-- package-lock.json
    	|-- package.json
    	|-- src
    	|   |-- dep.ts
    	|   |-- effect.ts
    	|   |-- handler.ts
    	|   |-- reactive.ts
    	|   `-- utils.ts
    	|-- tsconfig.json
    	`-- vite.config.ts
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    reactive.ts

    初步实现:

    基本功能:将object类型数据处理为响应式数据。
    基本实现:

    1. 将target处理成proxy实例,如果属性为对象或数组,则递归调用reactive(res)进一步代理。将每个proxy实例放到全局map。
    const proxyMap = new WeakMap();
    
    export const reactive = function <T extends object>(target: T): T {
      const handler: ProxyHandler<any> = {
        get(target, key, receiver) {
          const res = Reflect.get(target, key, receiver);
          if (isObject(res)) return reactive(res);
          return res;
        },
        set(target, key, newVal, receiver) {
          return Reflect.set(target, key, newVal, receiver);
        },
      };
      const existingProxy = proxyMap.get(target);
      if (existingProxy) return existingProxy;
      const proxy = new Proxy<T>(target, handler);
      proxyMap.set(target, proxy);
      return proxy;
    };
    
    export const isObject = (target: any) =>   
      typeof target === "object" && target !== null;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    1. 将headler抽离到handler.tsisObject放到utils.ts

    reactive.ts

    import { handler } from './handler';
    const proxyMap = new WeakMap();
    
    export const reactive = function <T extends object>(target: T): T {
      const existingProxy = proxyMap.get(target);
      if (existingProxy) return existingProxy;
      const proxy = new Proxy<T>(target, handler);
      proxyMap.set(target, proxy);
      return proxy;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    handler.ts

    初步实现

    将getter和setter抽离,在get时触发track追踪收集依赖,在set时触发trigger触发依赖更新。

    import { reactive } from "./reactive";
    import { isObject } from "./utils";
    
    export const handler: ProxyHandler<any> = {
      get: createGetter(),
      set: createSetter(),
    };
    
    const createGetter = () => {
      return function get(target, key, receiver) {
        const res = Reflect.get(target, key, receiver);
        // 追踪收集依赖
        track(res)
        if (isObject(res)) return reactive(res);
        return res;
      };
    };
    
    const createSetter = () => {
      return function set(target, key, newVal, receiver) {
        // 触发依赖的数据或视图更新
        trigger(target)
        return Reflect.set(target, key, newVal, receiver);
      };
    };
    
    • 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

    effect.ts

    实现Class ReactiveEffecteffect,最后实现tracktrigger

    class ReactiveEffect

    基本功能:_fn属性保存视图或数据更新函数,响应式数据被获取时触发getter,ReactiveEffect实例会被对应的depsMap收集,当响应式数据被修改时,会遍历depsMap,调用每一项(即ReactiveEffect实例)的run方法进而调用更新函数_fn,实现视图更新。

    track

    基本功能:追踪依赖。
    实现方式:全局申明一个targetMap,利用target从targetMap中获取到依赖Map即depsMap,利用key值从depsMap中获取对应的deps。deps是一个集合Set,每一项都是一个ReactiveEffect实例(后边会实现)。

    // 类型
    type ReactiveEffect = {
    	run: () => any
    }; // 暂时不管,后续会实现Class ReactiveEffect
    type DepsMap = Set<ReactiveEffect>;
    
    const targetMap = new Map<any, Map<string, DepsMap>>();
    let reactiveEffect: ReactiveEffect | undefined;
    
    export const track = (target: any, key: string) => {
      if (reactiveEffect) {
        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<ReactiveEffect>()));
        trackEffect(deps);
      }
    };
    
    const trackEffect = (deps: DepsMap) => {
      deps.add(reactiveEffect as ReactiveEffect);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    注:targetMap结构

    targetMap: Map {
    	target1: Map {
    		key1: Set,
    		key2: Set
    	},
    	target2: Map {
    		key1: Set
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    trigger

    基本功能:让数据对应的依赖调用run方法,实现视图或数据更新。
    实现方式:获取当前target当前key的deps,deps转化为数组effects: ReactiveEffect[],遍历effects调用ReactiveEffect.run方法。

    export const trigger = (target: any, key: string) => {
      let depsMap = targetMap.get(target) as Map<string, DepsMap>;
      let deps = depsMap.get(key) as DepsMap;
      triggerEffect([...deps])
    };
    
    const triggerEffect = (effects: ReactiveEffect[]) => {
      for (let effect of effects) effect.run()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    effect

    基本功能:每次视图或数据使用到某一个响应式数据时,将视图或数据更新函数fn作为

    完整过程:

    1. 假设响应式数据A,视图X和Y都用到了A。
      <div id="x">我是X,我使用到{{A}}<div>
      <div id="y">我是Y,我使用到{{A}}<div>
    ...
    const obj = reactive({
    	A: "qq"
    })
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. X使用到A之前会先调用effect函数,将当前X的更新函数fn传入。
    // 调用effect生成effect实例
    effect(fn);
    
    setTimeout(() => {
      obj.A = "wechat";
    }, 1000);
    
    // x元素的视图更新函数
    function fn() {
      resetXElement();
      const $x = document.querySelector("#x");
      $x && ($x.innerHTML = $x.innerHTML.replace(/\{\{A\}\}/, obj.A));
    }
    
    // 由于模板{{A}}被替换成A的值,每次都要重置模板
    function resetXElement() {
      const $x = document.querySelector("#x");
      $x && ($x.innerHTML = "我是X,我使用到{{A}}");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    1. effect函数传入fn生成ReactiveEffect实例放到全局的变量reactiveEffect,通过调用实例的run方法进而调用fn
    let reactiveEffect;
    
    function effect(fn) {
    	const _effect = new ReactiveEffect(fn)
    	_effect.run()
    }
    
    class ReactiveEffect {
    	...
    	constructor(fn) {
    		this._fn = fn
    	}
    	run() {
    		reactiveEffect = this
    		return this._fn()
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    1. fn访问到A,触发A的getter,进而触发track,由于全局的变量reactiveEffect有值,数据需要被track,找到A对应的depsMap,将实例放入其中。
    function fn() {
    	...
    	// 访问到了obj.A
      	$x && ($x.innerHTML = $x.innerHTML.replace(/\{\{A\}\}/, obj.A));
    }
    
    // 触发getter
    function get(target, key) {
    	track()
    }
    // 触发track
    function track(target, key) {
    	if (reactiveEffect) {
    		// 由target(即此处的obj)获取depsMap
    		const depsMap = targetMap.get(target)
    		// 由key(即A)获取deps
    		const deps = depsMap.get(key)
    		// 收集依赖
    		deps.add(reactiveEffect) // 此时的deps为Set[reactiveEffect1],reactiveEffect1中有x的更新函数
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    1. Y使用到A同理,创建一个ReactiveEffect实例,会被A对应的depsMap收集起来。

    2. 当A数据发生改变时,只要找到对应的depsMap,遍历调用每一项的run方法,进而调用fn,实现视图或数据的更新。

    完整代码

    src目录里的文件

    // reactive.ts
    import { handler } from './handler';
    const proxyMap = new WeakMap();
    
    export const reactive = function <T extends object>(target: T): T {
      const existingProxy = proxyMap.get(target);
      if (existingProxy) return existingProxy;
      const proxy = new Proxy<T>(target, handler);
      proxyMap.set(target, proxy);
      return proxy;
    };
    
    
    
    // handler.ts
    import { reactive } from "./reactive";
    import { isObject } from "./utils";
    import { track, trigger } from "./effect";
    
    const createGetter = () => {
      return function get(target: Object, key: string, receiver: any) {
        const res = Reflect.get(target, key, receiver);
        track(target, key);
        if (isObject(res)) return reactive(res);
        return res;
      };
    };
    
    const createSetter = () => {
      return function set(target: Object, key: string, newVal: any, receiver: any) {
        trigger(target, key);
        return Reflect.set(target, key, newVal, receiver);
      };
    };
    
    export const handler: ProxyHandler<any> = {
      get: createGetter(),
      set: createSetter(),
    };
    
    
    
    
    // effect.ts
    import { Dep } from "./dep";
    
    type DepsMap = Set<ReactiveEffect>;
    
    const targetMap = new Map<any, Map<string, DepsMap>>();
    let reactiveEffect: ReactiveEffect | undefined;
    
    export const track = (target: any, key: string) => {
      if (reactiveEffect) {
        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<ReactiveEffect>()));
        trackEffect(deps);
      }
    };
    
    const trackEffect = (deps: DepsMap) => {
      deps.add(reactiveEffect as ReactiveEffect);
    };
    
    export const trigger = (target: any, key: string) => {
      let depsMap = targetMap.get(target) as Map<string, DepsMap>;
      let deps = depsMap.get(key) as DepsMap;
      triggerEffect([...deps]);
    };
    
    const triggerEffect = (effects: ReactiveEffect[]) => {
      for (let effect of effects) effect.run();
    };
    
    export const effect = function <T>(fn: (...arg: any) => T) {};
    
    export class ReactiveEffect<T = any> {
      private _fn: any;
      constructor(fn: () => T) {
        this._fn = fn;
      }
      run() {
        reactiveEffect = this;
        this._fn();
      }
    }
    
    
    
    // dep.ts
    import { ReactiveEffect } from "./effect"
    
    export type Dep = Set<ReactiveEffect> & {w:number, n:number}
    
    export const createDep = (effects?: any): Dep => {
      const dep = new Set<ReactiveEffect>(effects) as Dep
      dep.w = 0
      dep.n = 0
      return dep
    }
    
    
    
    // utils
    export const isObject = (target: any) =>   
      typeof target === "object" && target !== null;
    
    
    • 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
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108

    index.html

    DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Documenttitle>
    head>
    <body>
      
      <script src="./index.ts" type="module">script>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    index.ts

    import { effect } from "./packages/reactivity/src/effect";
    import { reactive } from "./packages/reactivity/src/reactive";
    const obj = reactive({
      A: "qq",
    });
    
    window.addEventListener("load", handler);
    
    function handler() {
      let fnX = updateXElement
      effect(fnX);
      setTimeout(() => {
        obj.A = "wechat";
      }, 1000);
    }
    
    function updateXElement() {
      let $x = document.querySelector("#x");
      resetXElement($x as Element);
      $x && ($x.innerHTML = $x.innerHTML.replace(/\{\{A\}\}/, obj.A));
    }
    
    function resetXElement($x: Element) {
      $x && ($x.innerHTML = "我是X,我使用到{{A}}");
    }
    
    • 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

    vite.config.ts

    import { defineConfig } from "vite";
    
    // https://vitejs.dev/config/
    export default defineConfig({
      server: {
        port: 3000,
      },
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    package.json

    ...
      "scripts": {
        "dev": "npx vite --host"
      },
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
  • 相关阅读:
    【Java】字符串中的数据排序
    AI创作系统ChatGPT网站源码+详细搭建部署教程+支持DALL-E3文生图/支持最新GPT-4-Turbo-With-Vision-128K多模态模型
    1024程序员节献礼,火山引擎ByteHouse带来三重产品福利
    xlwings笔记
    【pytest】html报告修改和汉化
    Linux centos环境 安装谷歌浏览器
    【Hello Algorithm】暴力递归到动态规划(二)
    P10 属性分组
    国网云(华为组件)使用
    京东云开发者|探寻软件架构的本质,到底什么是架构?
  • 原文地址:https://blog.csdn.net/weixin_39503495/article/details/127204881