从零实现vue3核心源码 day3

今天主要来学习reactive和effect和计算属性的具体实现。下面代码示例部分只做参考,具体完整代码可以在vue3源码解读: 手写vue3核心源码,内含详细解读 (gitee.com)内查看。

1. 修改属性需要每次都更新吗?

src/1.reactive.html

js 复制代码
import { reactive, effect } from "../js/reactivity";

const data = {
  name: "jw",
  age: 30,
  address: "zz",
  user: {
    post: "qd",
  },
  flag: false,
};
const state2 = reactive(data);

const state3 = reactive(state2);

const runner = effect(
    () => {
        console.log("runner");
        app.innerHTML = state3.name;
    }
);
// state3.name = 1
// state3.name = 2
// state3.name = 3

如上代码所示,我们修改了3次 name 属性,如果不作任何处理一定会更新3次,而只有最后一次结果是我们需要的,这样无疑是浪费性能的。我们需要把它们进行合并更新。

js/reactivity/effect.js

ini 复制代码
let isFlushing = false;
const runner = effect(
    () => {
        console.log("runner");
        // state3.user 如果这个也是对象,我们应该针对这个对象进行代理
        // 懒代理
        app.innerHTML = state3.name;
    },
    {
        //     调度执行
        scheduler() {// effect.run()
            if (!isFlushing) {
                Promise.resolve().then(() => {
                    //     异步更新
                    runner();
                    isFlushing = false;
                });
                isFlushing = true;
            }
        },
    }
);

js/reactivity/handler.js

js 复制代码
export function triggerEffects(effects) {
  if (effects) {
    effects = [...effects];
    effects.forEach((effect) => {
      // 当前正在执行的和现在正要执行的是同一个我就屏蔽掉
      if (activeEffect !== effect) {
        if (effect.scheduler) {
          // 有 scheduler 应该先执行 scheduler
          effect.scheduler();
        } else {
          effect.run(); // 里边有删除 + 添加的逻辑
        }
      }
    });
  }
}

我们可以把是否渲染的逻辑交给用户,首先定义了isFlushing 是否更新变量,在 effect 函数第二个参数传入对象。

scheduler 是用户自定义的渲染逻辑,如果用户传入 scheduler 优先执行 scheduler,执行 scheduler 的同时,将 isFlushing 状态改为正在更新,这样下次更新 state3.name 不会再次进入,利用 promise 异步的方式执行 effect.run() 重新渲染,这时候同步代码已经执行完毕了, name 渲染出来的就只能是3了。

这个原理类似于防抖,只执行最后一次的结果。

2 深度代理

src/1.reactive.html

js 复制代码
const data = {
    user: {
        post: "qd",
    },
};
const state3 = reactive(state2);
const runner = effect(
    () => {
        console.log("runner");
        // state3.user 如果这个也是对象,我们应该针对这个对象进行代理
        // 懒代理
        app.innerHTML = state3.user.post;
    }
);

state3.user.post = "hd";

由于我们只做了一层代理,访问 state3.user 和 修改 state3,user 是可以更新的,也就是说 state3.user = {post:'ad'} 是可以更新的。

如果对象的属性内有对象, 我们也应该针对于这个对象进行代理。这也是一个懒代理,也就是说只有读取到这个属性才进行代理,还没被读取的时候是不进行代理的。

js/reactivity/handler.js

js 复制代码
get(target, key, receiver) {
  // 走到这里说明已经是代理对象, 可返回true
  if (key === ReactiveFlags.IS_REACTIVE) {
    return true;
  }
  // console.log('取值的时候关联effect')
  track(target, key);

  let result = Reflect.get(target, key, receiver);
  if (isObject(result)) {
    // 如果取到的是对象,则需要继续将这个对象作为代理对象
    return reactive(result);
  }
  return result;
},

3. effect 是什么?

effect 是 vue 比较偏底层的方法,我们常用的 computed, watch, watchEffect 都是基于 effect 去实现的,就像搭积木一样。

4. 计算属性的特点

  • 1) 不访问这个属性就不计算
  • 2) 计算属性得有同步的返回值,不能异步
  • 3) 如果依赖的值不变也不重新计算
  • 4) 计算属性不能修改本身的值,可以去修改其它属性
  • 5) 可以在模板中使用

5. 计算属性实现

核心实现代码

src/2.computed.html

javascript 复制代码
const state = reactive({
  firstName: "j",
  lastName: "w",
  age: 30,
});
const fullName = computed({
  get: () => {
    console.log("computed");
    return state.firstName + state.lastName;
  },
  set: (val) => {
    console.log(val);
  },
});

// 计算属性也是一个effect, 依赖的状态会收集计算属性的 effect
// 计算属性会触发它收集的 effect

effect(() => {
  // 计算属性也可以收集effect
  console.log("计算属性也可以收集effect");
  // 多次获取计算属性,依赖属性未发生变化,只计算一次(this._dirty的作用)
  console.log(fullName.value);
  console.log(fullName.value);
  console.log(fullName.value);
  console.log(fullName.value);

js/reactivity/computed.js

kotlin 复制代码
// 计算属性是一个effect,会让 getter 中的属性去收集这个 effect
this.effect = new ReactiveEffect(getter, () => {
    //   ...
    this._dirty = true // 让计算属性标记为脏值
    // 当 getter 触发依赖更新的时候,也重新触发 计算属性 的依赖更新
    triggerEffects(this.dep)
});


get value() {
    if (activeEffect) {
        //   value => [effect, effect, effect]
        trackEffects(this.dep)
    }
    if (this._dirty) {
        this._dirty = false;
        // 取值让 getter 执行拿到返回值,作为计算属性旳值
        // 当取计算属性的时候, 让 getter 执行触发依赖收集
        this._value = this.effect.run();
    }

    return this._value;
}

计算属性就是一个 effect, 在执行 effect 的时候,读取计算属性触发 get 访问器,将当前 activeEffect 加入到当前计算属性的依赖里。 在计算属性 get 方法内部依赖触发更新时,我们也让计算属性触发依赖更新。

计算属性的不同点

计算属性的不同点是在于 值对应effect, 而不是对象属性对应 effect

对象名字对应effect

css 复制代码
Map1 = {({name: 'jw',age:30}):Map2}
Map2 = {name: new Set([effect, effect])}

计算属性对应effect

ini 复制代码
value => [effect, effect, effect]

多次获取计算属性

多次获取计算属性,依赖属性未发生变化,只计算一次(this._dirty的作用)

在创建计算属性的时候,增加 _dirty 变量记录是否为脏值,第一次取值为 fasle, 执行 计算属性的 get 方法,然后将它改为 false, 当第二次进来因为 false 就不会再执行了。只有 get 内部的依赖触发重新更新时,将它再改为 true,这时候就又能执行了。

相关推荐
whysqwhw5 分钟前
鸿蒙工程版本与设备版本不匹配
前端
gnip12 分钟前
http缓存
前端·javascript
我不只是切图仔1 小时前
我只是想给网站加个注册验证码,咋就那么难!
前端·后端
该用户已不存在1 小时前
macOS是开发的终极进化版吗?
前端·后端
小豆包api1 小时前
小豆包AI API × Nano Banana:3D手办 + AI视频生成,「动起来」的神级玩法!
前端·api
布列瑟农的星空2 小时前
大话设计模式——观察者模式和发布/订阅模式的区别
前端·后端·架构
龙在天2 小时前
Vue3 实现 B站 视差 动画
前端
KenXu2 小时前
F2C Prompt to Design、AI 驱动的设计革命
前端
小鱼儿亮亮2 小时前
canvas中画线条,线条效果比预期宽1像素且模糊问题分析及解决方案
前端·react.js
@大迁世界2 小时前
用 popover=“hint“ 打造友好的 HTML 提示:一招让界面更“懂人”
开发语言·前端·javascript·css·html