vue3-响应式基础篇

102 阅读1分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情

流程

1.创建一个响应式对象

2 effect执行时,把当前执行的effect赋值给全局变量,执行时由于访问到proxy属性,可以在proxy里的get方法里面收集依赖,依赖格式:weakmap { 响应式对象 :{(map、属性} :{ set、effect } }

3 数据变化时,触发proxyset,赋值后执行trigger,执行数据对应的effect依赖。

依赖收集样式

配合下面的简单版例子

image-20220531231459132.png

vue2的缺点

1 使用 defineProperty 进行数据劫持,需要重改 数据的 gettersetter

2 新增和删除属性无法监控变化,需要 用 $set/delete

3 数组需要单独处理,map、set不能处理

vue3优点

采用WeakMap,当依赖清除时,能及时去掉不必要的内存占用

demo

3s后 state.age变化,通过state.age渲染的元素也跟着变化

<div id="app"></div> 
<script src="../dist/reactivity.global.js"></script>
    <script>
    //打包的全局的名字 VueReactivity
    // effect:副作用,开始会执行一次,依赖变动会执行
        const { reactive, effect } = VueReactivity;
        const state = reactive({ name: 'zf', age: 13 ,address:'xxx'});
        // 1.effect执行的过程是默认执行一次,使用的数据更新了 会再次执行
        let runner = effect(() => {
            app.innerHTML = state.name + '今年' + state.age + '岁了' + state.age;
        })
        setTimeout(() => {
            state.age = 20;
        }, 3000)
    </script>

简单版

可以自行调试,由于我用的是es6引入,所以需要开启静态服务器,不然浏览器会报错的。

我用的是http-server

  • 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>Document</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module">
      import { effect, reactivity } from "./index.js";
      const obj = reactivity({ age: 1 });
      effect(() => {
        document.getElementById("app").innerHTML = `我${obj.age}岁`;
        console.log(`我${obj.age}岁`);
      });
      setTimeout(() => {
        obj.age = 2333;
      }, 1000);
    </script>
  </body>
</html>
  • index.js
// 用于存储当前执行的effect
let targetEffect
// 用于存储依赖映射关系
let effectMap = new WeakMap()
// proxy 
export const reactivity = function (target) {
    return new Proxy(target, {
        get(target, key, receiver) {
            // track 收集依赖
            track(target, key)
            return target[key]
            // return Reflect.get(target, key, receiver)
        },
        set(target, key, value, receiver) {
            // trigger 这里因为我搞错顺序,导致一直调试不对哎,要先赋值再去trigger
            target[key] = value
            // return Reflect.set(target, key, value, receiver)
            // 数据更新,执行这个数据对应的effect依赖们
            trigger(target, key)
        }
    })
}
// effect 执行时,存到全局变量,准备依赖收集
export const effect = function (fn) {
    targetEffect = fn
    fn()
}
// weekmap:map:set
// 对象 : 属性 : effect
const track = function (target, key) {
    let obj = effectMap.get(target)
    if (!obj) {
        effectMap.set(target, (obj = new Map()))
    }
    let att = obj.get(key)
    if (!att) {
        obj.set(key, (att = new Set()))
    }
    if (!att.has(targetEffect)) {
        att.add(targetEffect)
    }
}
const trigger = function (target, key) {
    effectMap.get(target).get(key).forEach(i => i())
}