最浅显易懂的 Vue3 响应性原理

大家好,我是哈默。今天我们来学习一下 vue3 中的 响应性原理

响应性数据和副作用函数

现在,假如我有一个响应性的数据:

js 复制代码
const data = {
  name: "哈默",
  age: 20,
  msg: "hello",
};

页面上有一处 div#app 的容器:

js 复制代码
<div id="app"></div>

那我们可以通过一个副作用函数 effect 将响应性数据里面的 msg 渲染到 div#app 中。

js 复制代码
function effect() {
  document.getElementById("app").innerText = obj.msg;
}

这个时候,我们执行 effect 函数:

js 复制代码
effect();

页面上就可以成功的渲染出来我们的 obj.msg 的值 hello。

那么,现在我们的目标是:

在数据发生改变时,重新渲染视图。

而我们知道,vue3 中是使用 Proxy 来监听数据的读取和设置操作的,所以我们可以将 data 进行改造:

js 复制代码
const data = {
  name: "哈默",
  age: 20,
  msg: "hello",
};

const obj = new Proxy(data, {
  get(target, key) {
    console.log("读取了属性");
    return data[key];
  },
  set(target, key, value) {
    console.log("设置了属性");
    target[key] = value;

    return true;
  },
});

这个时候,我们就可以使用代理对象 obj 来访问被代理对象 data 里的属性或者设置属性:

js 复制代码
obj.msg;
obj.msg = "bye";

控制台中,就会打印出我们读取和设置的行为:

现在,我们想要在设置响应性数据的时候,重新渲染视图。

比如将 obj.msg 从 hello 变成了 bye,我们希望页面上也能从 hello 变成 bye。

那么,我们只需要重新执行 effect 就可以了:

js 复制代码
function effect() {
  document.getElementById("app").innerText = obj.msg;
}

effect 就会把我们目前 obj.msg 的值渲染到对应的 div#app 中。

问题是:我们如何能够在 obj.msg 的值发生改变的时候,重新执行 effect 呢?

我们在设置 obj.msg 的时候,会触发 set 行为:

js 复制代码
set(target, key, value) {
  console.log("设置了属性");
  target[key] = value;

  // TODO: 重新执行 effect

  return true;
}

所以我们只要在 set 行为里重新执行 effect 就好了。

因为我们在第一次将 obj.msg 渲染到页面上的时候,需要获取 obj.msg 的值,才能知道要渲染什么内容到页面上。

那么,在获取 obj.msg 的时候,也就是 get 行为的时候,我们就可以将 effect 存在一个容器里面。

然后我们就可以在设置 obj.msg 的时候,也就是 set 行为时,将容器里的 effect 依次执行。

diff 复制代码
+ const fns = new Set()

const obj = new Proxy(data, {
  get(target, key) {
    console.log("读取了属性");
+   fns.add(effect)
    return data[key];
  },
  set(target, key, value) {
    console.log("设置了属性");
    target[key] = value;

+   fns.forEach(fn => fn())

    return true;
  },
});

来看下效果:

现在,我们就完成了一个最基本的响应性的实现。

但是,现在我们有 2 个问题:

  1. effect 执行的逻辑是硬编码的。
  2. 无论我们修改 obj 的什么属性,都会重新执行 effect。

问题 1

我们现在的 effect 只能做一件事情:

js 复制代码
function effect() {
  document.getElementById("app").innerText = obj.msg;
}

所以,我们需要将其改造一下:

diff 复制代码
+ let activeEffect;

- function effect() {
-   document.getElementById("app").innerText = obj.msg;
- }
+ function effect(fn) {
+  activeEffect = fn;
+  fn();
+ }

const obj = new Proxy(data, {
  get(target, key) {
-   fns.add(effect)
+   fns.add(activeEffect);
    return data[key];
  },
  set(target, key, value) {
    target[key] = value;

    fns.forEach((fn) => fn());

    return true;
  },
});

我们让 effect 接受一个参数 fn,这样我们就可以执行多个副作用函数。

js 复制代码
effect(fn1); // fn1 会更新页面上 p1 元素
effect(fn2); // fn2 会更新页面上 p2 元素

问题 2

我们先来看一下目前修改一个属性的时候,所有 effect 都会执行的现象。

代码:

diff 复制代码
const data = {
  name: "哈默",
  age: 20,
  msg: "hello",
};

let activeEffect;

function effect(fn) {
  activeEffect = fn;

  fn();
}

const fns = new Set();

const obj = new Proxy(data, {
  get(target, key) {
    console.log("读取了属性");
    fns.add(activeEffect);
    return data[key];
  },
  set(target, key, value) {
    console.log("设置了属性");
    target[key] = value;
    fns.forEach((fn) => fn());

    return true;
  },
});

+ effect(() => {
+   console.log("根据 msg 属性的值,渲染 p1 的内容");
+   document.getElementById("p1").innerText = obj.msg;
+ });
+ effect(() => {
+   console.log("根据 age 属性的值,渲染 p2 的内容");
+   document.getElementById("p2").innerText = obj.age;
+ });
+ effect(() => {
+   console.log("根据 name 属性的值,渲染 p3 的内容");
+   document.getElementById("p3").innerText = obj.name;
+ });

setTimeout(() => {
  obj.msg = "bye";
}, 5000);

我们改变 obj.msg 的值,从 hello 改成 bye,在页面上:

我们发现,除了出发了 msg 的重新渲染,age、name 相应的区域也发生了重新渲染。

所以我们需要建立起 effect 和某个对象某个属性之间的联系。

在 vue 中,最后是这样做的:

这样一来,我们的某一个 effect 就可以被放到某一个对象的某一个属性的一个 Set 数组中了。

当我们改变某一个响应性对象的属性,比如 msg 的时候,我们就只需要找到 msg 对应的那个 Set 数组,然后循环执行就可以了。

到这里,我们的响应性系统又进一步完善了一下。

总结

整个响应性的原理就是围绕着 Proxyeffect 来进行的,我们只需要能够在响应性数据发生变化的时候,正确地执行对应的 effect ,就可以实现一个响应性了。

相关推荐
码海扬帆:前端探索之旅4 小时前
深度定制 uni-combox:新增功能详解与实战指南
前端·vue.js·uni-app
谷雨不太卷4 小时前
进程的状态码
java·前端·算法
打小就很皮...4 小时前
基于 Python + LangChain + RAG 的知识检索系统实战
前端·langchain·embedding·rag
BJ-Giser5 小时前
Cesium 烟雾粒子特效
前端·可视化·cesium
空中海5 小时前
02 ArkTS 语言与工程规范
java·前端·spring
YJlio5 小时前
7.4.5 Windows 11 企业网络连接与网络重置实战:远程访问、本地策略与故障恢复
前端·chrome·windows·python·edge·机器人·django
Slow菜鸟5 小时前
Codex CLI 教程(五)| Skills 安装指南:面向 Java 全栈工程师打造个人 ECC(V1版)
大数据·前端·人工智能
Lee川5 小时前
打字机是怎么炼成的:Chat 流式输出深度解析
前端·后端·面试
前端若水5 小时前
过渡(transition)高级:贝塞尔曲线、硬件加速
前端·css·css3
Lee川5 小时前
Token 无感刷新与 Logout:前端安全会话管理实战
前端·后端·react.js