浅析 Vue3 响应式原理

更新日期: 2022-08-11阅读: 1.2k标签: 响应式

本 文 作 者 为   3 6 0   技 术 中 台效能工程 部 的 前 端 开 发 工 程 师

Proxy

vue3 的响应式原理依赖了 Proxy 这个核心 api,通过 Proxy 可以劫持对象的某些操作。

const obj = { a: 1 };
const p = new Proxy(obj, {
get(target, property, receiver) {
console.log("get");
return Reflect.get(target, property, receiver);
},
set(target, property, value, receiver) {
console.log("set");
return Reflect.set(target, property, receiver);
},
has(target, prop) {
console.log("has");
return Reflect.has(target, prop);
},
deleteProperty(target, prop) {
console.log("deleteProperty");
return Reflect.deleteProperty(target, prop);
},
});

p.a; // 输出 --> get
p.a = 2; // 输出 --> set
"a" in p; // 输出 --> has
delete p.a; // 输出 --> deleteProperty

如上例子,我们用 Proxy 代理了 Obj 对象的属性访问、属性赋值、in 操作符、delete 的操作,并进行 console.log 输出。

Reflect

Reflect 是与 Proxy 搭配使用的一个 API,当我们劫持了某些操作时,如果需要再把这些操作反射回去,那么就需要 Reflect 这个 API。

由于我们拦截了对象的操作,所以这些操作该有的功能都丧失了,例如,访问属性 p.a 应该得到 a 属性的值,但此时却不会有任何结果,如果我们还想拥有拦截之前的功能,那我们就需要用 Reflect 反射回去。

const obj = { a: 1 };
const p = new Proxy(obj, {
get(target, property, receiver) {
console.log("get");
return Reflect.get(target, property, receiver);
},
set(target, property, value, receiver) {
console.log("set");
return Reflect.set(target, property, receiver);
},
has(target, prop) {
console.log("has");
return Reflect.has(target, prop);
},
deleteProperty(target, prop) {
console.log("deleteProperty");
return Reflect.deleteProperty(target, prop);
},
});

举个例子

以下全文我们都会通过这个例子来讲述 Vue3 响应式的原理。

<div id="app"></div>

<script>
// 创建一个响应式对象
const state = reactive({ counter: 1 });

// 立即运行一个函数,当响应式对象的属性发生改变时重新执行。
effect(() => {
document.querySelector("#app").innerhtml = state.counter;
});

// 2s 后视图更新
setTimeout(() => {
state.counter += 1;
}, 2000);
</script>

我们用 reactive 创建了一个响应式对象 state,并调用了 effect 方法,该方法接受一个副作用函数,effect 的执行会立即调用副作用函数,并将 state.counter 赋值给 #app.innerHTML;两秒后,state.counter += 1,此时,effect 的副作用函数会重新执行,页面也会变成 2.

内部的执行过程大概如下图所示:


  1. 调用 reactive() 返回一个 Proxy 代理对象,并劫持对象的 get 与 set 操作

  2. 调用 effect() 方法时,会访问属性 state.counter,此时会触发 proxy 的 get 操作。

  3. get 方法会调用 track() 进行依赖收集;建立一个对象(state)、属性(counter)、effect 副作用函数的依赖关系;

  4. set 方法会调用 trigger() 进行依赖更新;通过对象(state)与属性(coutner)找到对应的 effect 副作用函数,然后重新执行。

reactive

reactive 会返回如下一个 Proxy 对象

const reactive = (target) => {
return new Proxy(target, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);

track(target, key); // 收集依赖

if (isObject(res)) {
// 如果当前获取的属性值是一个对象,则继续将为此对象创建 Proxy 代理
return reactive(res);
}

return res;
},

set(target, key, value, receiver) {
Reflect.set(target, key, value, receiver);
trigger(target, key); // 依赖更新
},
});
};

effect

let activeEffect;
function effect(fn) {
const _effect = function reactiveEffect() {
activeEffect = _effect;
fn();
};

_effect();
}

首先定义全局的 activeEffect,它永远指向当前正在执行的 effect 副作用函数。effect 为 fn 创建一个内部的副作用函数,然后立即执行,此时会触发对象的 get 操作,调用 track() 方法。

effect(() => {
// effect 的立即执行会访问 state.counter,触发了对象的 get 操作。
document.querySelector("#app").innerHTML = state.counter;
});

track

track 会建立一个 对象(state) => 属性(counter) => effect 的一个依赖关系

const targetMap = new WeakMap();
function track(target, key) {
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()));
}

if (!dep.has(activeEffect)) {
dep.add(activeEffect);
}
}

执行完成成后我们得到一个如下的数据结构:

[ // map 集合
{
key: {counter: 1} // state 对象,
value: [ // map 集合
{
key: "counter",
value: [ // set
function reactiveEffect() {} // effect 副作用函数
],
}
],
},
];

注意:当我们调用 effect 时,会将当前的副作用函数赋值给全局的 activeEffect,所以此时我们可以正确关联其依赖。

trigger

当我们给 state.counter 赋值的时候就会触发代理对象的 set 操作,从而调用 trigger 方法

setTimeout(() => {
// 给 counter 属性赋值会触发 set 操作
state.counter += 1;
}, 2000);
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;

const effects = depsMap.get(key);
effects && effects.forEach((effect) => effect());
}
原文 https://mp.weixin.qq.com/s/ylf8E2hYzYpBtXxl4NakRg

链接: https://fly63.com/article/detial/11996

移动端web app要使用rem实现自适应布局:font-size的响应式

rem是相对于根元素html,这样就意味着,我们只需要在根元素确定一个px字号,则可以来算出元素的宽高。

使用现代CSS的响应式版面

通过模块化缩放,使用传统属性和calc()来动态缩放你的字体大小.为字体大小使用百分比.给文本内容和媒体查询使用em,针对不同视口尺寸使用不同缩放值.视口越小,缩放比例越小,使用媒体查询或者media()函数基于视口来改变比例和基础字号

web响应式图片的5种实现

在目前的前端开发中,我们经常需要进行响应式的网站开发。本文着重介绍一下弹性图片,也就是响应式图片的解决方案:js或服务端、srcset 、sizes 、picture标签、svg图片

HTML5+CSS3响应式垂直时间轴,高端,大气

HTML5+CSS3响应式垂直时间轴,使用了HTML5标签<section>,时间轴中所有的内容包括标题、简介、时间和图像都放在.cd-timeline-block的DIV中,多个DIV形成一个序列,并把这些DIV放在<section>中。

实现响应式_CSS变量

CSS 变量是 CSS 引入的一个新特性,目前绝大多数浏览器已经支持了,它可以帮助我们用更少的代码写出同样多的样式,大大提高了工作效率,本篇文章将教你如何使用 CSS 变量(css variable)。CSS中原生的变量定义语法是:--*,变量使用语法是:var(--*),其中*表示变量名称

vue响应式原理及依赖收集

Vue通过设定对象属性的setter/getter方法来监听数据的变化,通过getter进行依赖收集,而每个setter方法就是一个观察者,在数据变更的时候通知订阅者更新视图。

vue响应式系统--observe、watcher、dep

Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的JavaScript 对象,而当你修改它们时,视图会进行更新,这使得状态管理非常简单直接,我们可以只关注数据本身

Responsive Web Design 响应式网页设计

常见的布局方案:固定布局:以像素作为页面的基本单位,不管设备屏幕及浏览器宽度,只设计一套尺寸;可切换的固定布局:同样以像素作为页面单位,参考主流设备尺寸

响应式布局的实现

响应式布局,即 Responsive design,在实现不同屏幕分辨率的终端上浏览网页的不同展示方式。通过响应式设计能使网站在手机和平板电脑上有更好的浏览阅读体验。响应式布局的关键不仅仅在于布局

深入响应式原理

说到响应式原理其实就是双向绑定的实现,说到 双向绑定 其实有两个操作,数据变化修改dom,input等文本框修改值的时候修改数据1. 数据变化 -> 修改dom;2. 通过表单修改value -> 修改数据

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!