前言
版本:3.0.2
说明:通过源码对Vue3的响应式原理进行阐述。
一、Vue2.x和Vue3.x实现响应式的基础
Object.defineProperty
Vue2.x
能够实现双向数据绑定,离不开Object.defineProperty
的支持。
局限性:
- 不能监测到对象属性的添加和删除,
Vue2.x
之所以能够监测到对象属性的添加和删除,是因为Vue2.x
封装了Vue.set
和vm.$set
进行对象属性的添加,使用Vue.delete
和vm.$delete
进行对象的删除。 - 无法监控数组下标的变化。这个局限性并不是
Object.defineProperty
的问题,而是Vue2.x
放弃了这个特性。至于原因,因为性能问题。
Proxy
代理器
Vue3.x
使用Proxy
来为对象添加一层拦截。
- 使用:
const proxy = new Proxy(target, handler);
其中,target
为目标对象;handler
为配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。
举个栗子
let person = {
name: "张三",
age: 20
}
let newPerson = new Proxy(person, {
// 拦截取值
get: function (target /* 目标对象 */, key /* 目标对象的属性名 */, receiver /* newPerson */) {
return "取值拦截:" + target[key] + `; key => ${key}`;
},
// 拦截赋值
set(target /* 目标对象 */, key /* 目标对象的属性名 */, value /* 目标对象的属性值 */, receiver /* newPerson */) {
target[key] = value;
console.log("赋值拦截:" + target[key] + `; key => ${key} ` + ` value => ${value}`); // => 赋值拦截:30; key => age value => 30
}
});
console.log(newPerson.name); // => 取值拦截:张三; key => name
newPerson.age = 30;
console.log(newPerson.age); // => 取值拦截:30; key => age
二、几个响应式API的使用
reactive
返回对象的响应式副本,深层的响应式代理,将会影响所有嵌套的属性。
html
<div id="app">
<div>{{ object.person.age }}</div>
<button @click="add">添加</button>
</div>
JavaScript
const { createApp, reactive } = Vue;
const app = createApp({
setup() {
const object = reactive({
person: {
age: 18
}
});
return {
object
}
},
methods: {
add() {
// 修改object.person.age的值,页面实时更新
this.object.person.age += 1;
}
}
}).mount("#app");
readonly
深层只读的响应式代理。只读表示:不可变更。
html
<div id="app">
<div>{{ reactiveObj.count }}</div>
<div>{{ readonlyObj.count }}</div>
<button @click="addReactive">addReactive</button>
<button @click="addReadonly">addReadonly</button>
</div>
JavaScript
const { createApp, reactive, readonly } = Vue;
const app = createApp({
setup() {
const reactiveObj = reactive({ count: 1 });
const readonlyObj = readonly(reactiveObj);
return {
reactiveObj,
readonlyObj
}
},
methods: {
addReactive() {
// 修改reactiveObj.count的值,会影响readonlyObj.count的值
this.reactiveObj.count += 1;
},
addReadonly() {
// 修改失败,只读对象不可变更,并导致警告
this.readonlyObj.count += 1;
}
}
}).mount("#app");
演示
shallowReactive
浅层的响应式代理
html
<div id="app">
<div>{{ shallowReactiveObj.count }}</div>
<div>{{ shallowReactiveObj.nested.count }}</div>
<button @click="addShallow">addShallow</button>
<button @click="addNest">addNest</button>
</div>
JavaScript
const { createApp, shallowReactive } = Vue;
const app = createApp({
setup() {
const shallowReactiveObj = shallowReactive({
count: 1,
nested: {
count: 1
}
});
return {
shallowReactiveObj
}
},
methods: {
addShallow() {
// 修改最外层的属性,页面实时响应
this.shallowReactiveObj.count += 1;
},
addNest() {
// 修改嵌套属性,页面不能实时响应
this.shallowReactiveObj.nested.count += 1;
console.log(this.shallowReactiveObj.nested.count);
}
}
}).mount("#app");
演示
shallowReadonly
浅层只读的响应式代理
html
<div id="app">
<div>{{ shallowReadonlyObj.count }}</div>
<div>{{ shallowReadonlyObj.nested.count }}</div>
<button @click="addShallow">addShallowReadonly</button>
<button @click="addNest">addNestReadonly</button>
</div>
JavaScript
const { createApp, reactive, shallowReadonly } = Vue;
const app = createApp({
setup() {
const reactiveObj = reactive({
count: 1,
nested: {
count: 1
}
});
const shallowReadonlyObj = shallowReadonly(reactiveObj);
return {
shallowReadonlyObj
}
},
methods: {
addShallow() {
// 修改最外层的属性,页面不能实时响应
this.shallowReadonlyObj.count += 1;
},
addNest() {
// 因为shallowReadonly代理的目标对象是reactiveObj(响应式对象)
// 修改嵌套属性,页面实时响应
this.shallowReadonlyObj.nested.count += 1;
}
}
}).mount("#app");
演示
三、模拟源码
通过模拟源码来一步步实现Vue3的响应式原理。
1、添加拦截,在
get
时,追踪目标对象,添加响应处理函数;在set
时,触发响应式函数。
创建响应式对象,参数为目标对象和配置对象,返回一个代理对象。
function createReactiveObject(target, baseHandlers) {
const proxy = new Proxy(target, baseHandlers);
return proxy;
}
creative
深层响应式代理
// 创建get拦截,第一个参数表示是否为只读,第二个参数表示是否浅层
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
if (!isReadonly) {
track(target, "get" /* GET */ , key);
}
// 如果为浅层监听,则直接返回res
if (shallow) {
return res;
}
// 深层属性添加响应
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res);
}
return res;
}
}
// 创建set拦截
function createSetter() {
return function set(target, key, value, receiver) {
const oldValue = target[key];
// 设置新值
const result = Reflect.set(target, key, value, receiver);
// 如果新值和旧值不一样,则触发赋值触发器
if (hasChanged(value, oldValue)) {
trigger(target, "set", key, value, oldValue);
}
return result;
}
}
// get拦截
const get = createGetter();
// set拦截
const set = createSetter();
const mutableHandlers = {
get,
set,
}
// 创建深层响应式对象
function reactive(target) {
return createReactiveObject(target, mutableHandlers);
}
readonly
只读响应式代理
// 只读的get拦截
const readonlyGet = createGetter(true);
const readonlyHandlers = {
get: readonlyGet,
set(target, key) {
{
// 目标对象是只读的,不能重新赋值
console.warn(`Set operation on key "${String(key)}" failed: target is readonly.`, target);
}
return true;
},
deleteProperty(target, key) {
{
// 目标对象是只读的,不能删除属性
console.warn(`Delete operation on key "${String(key)}" failed: target is readonly.`, target);
}
return true;
}
};
// 创建只读的响应式对象
function readonly(target) {
return createReactiveObject(target, readonlyHandlers);
}
2、追踪目标对象和触发响应式函数
effect
// effect栈
const effectStack = [];
// 当前活跃的响应处理函数,在执行effect时添加
let activeEffect;
function createReactiveEffect(fn) {
const effect = function reactiveEffect() {
try {
if (!effectStack.includes(effect)) {
effectStack.push(effect);
activeEffect = effect;
return fn();
}
} finally {
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1];
}
}
effect.deps = [];
return effect;
}
function effect(fn, options) {
const effect = createReactiveEffect(fn, options);
effect();
}
track
const targetMap = new WeakMap();
function track(target, type, key) {
if (activeEffect === undefined) {
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);
activeEffect.deps.push(dep);
}
}
trigger
function trigger(target, type, key, newValue, oldValue) {
const depsMap = targetMap.get(target);
if (!depsMap) {
return;
}
const effects = new Set();
const add = (effectsToAdd) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect) {
effects.add(effect);
}
})
}
};
if (key !== void 0) {
add(depsMap.get(key));
}
const run = (effect) => {
effect();
};
effects.forEach(run);
}
四、附:完整代码
html
<div id="app"></div>
<button id="addReactive">addReactive</button>
<button id="addReadonly">addReadonly</button>
JavaScript
function createAppAPI(render) {
return function createApp(rootComponent) {
const app = {
mount(rootContainer) {
render(rootComponent, rootContainer);
}
}
return app;
}
}
function ensureRenderer() {
// 安装effect
const setupRenderEffect = (rootComponent, rootContainer) => {
effect(function componentEffect() {
mountComponent(rootComponent, rootContainer);
});
}
const mountComponent = (rootComponent, rootContainer) => {
if (!rootComponent.isMounted) {
// 处理options
const { setup, methods, template } = rootComponent;
if (setup) {
const result = setup();
rootComponent.data = {};
Object.keys(result).forEach(key => {
// 触发get取值
rootComponent.data[key] = result[key];
});
}
const btnReactive = document.getElementById("addReactive");
const btnReadonly = document.getElementById("addReadonly");
btnReactive.addEventListener("click", () => {
methods.addReactive.call(rootComponent.data);
});
btnReadonly.addEventListener("click", () => {
methods.addReadonly.call(rootComponent.data);
});
rootComponent.isMounted = true;
}
// 每次响应,重新渲染页面
rootContainer.innerHTML = rootComponent.data.reactiveObj.person.age;
}
const render = (rootComponent, rootContainer) => {
setupRenderEffect(rootComponent, rootContainer);
}
return {
render,
createApp: createAppAPI(render)
}
}
function createApp(options) {
const app = ensureRenderer().createApp(options);
const { mount } = app;
// 重新定义mount
app.mount = (containerOrSelector) => {
const container = normalizeContainer(containerOrSelector);
if (!container) {
return;
}
mount(container);
}
return app;
}
// 格式化容器
function normalizeContainer(container) {
return document.querySelector(container);
}
const targetMap = new WeakMap();
// effect栈
const effectStack = [];
let activeEffect;
function createReactiveEffect(fn) {
const effect = function reactiveEffect() {
try {
if (!effectStack.includes(effect)) {
effectStack.push(effect);
activeEffect = effect;
return fn();
}
} finally {
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1];
}
}
effect.deps = [];
return effect;
}
function effect(fn, options) {
const effect = createReactiveEffect(fn, options);
effect();
}
function track(target, type, key) {
if (activeEffect === undefined) {
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);
activeEffect.deps.push(dep);
}
}
function trigger(target, type, key, newValue, oldValue) {
const depsMap = targetMap.get(target);
if (!depsMap) {
return;
}
const effects = new Set();
const add = (effectsToAdd) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect) {
effects.add(effect);
}
})
}
};
if (key !== void 0) {
add(depsMap.get(key));
}
const run = (effect) => {
effect();
};
effects.forEach(run);
}
const isObject = (val) => val !== null && typeof val === 'object';
// 创建get拦截,第一个参数表示是否为只读,第二个参数表示是否浅层
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
if (!isReadonly) {
track(target, "get" /* GET */ , key);
}
// 如果为浅层监听,则直接返回res
if (shallow) {
return res;
}
// 深层属性添加响应
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res);
}
return res;
}
}
const hasChanged = (value, oldValue) => value !== oldValue && (value === value || oldValue === oldValue);
// 创建set拦截
function createSetter() {
return function set(target, key, value, receiver) {
const oldValue = target[key];
// 设置新值
const result = Reflect.set(target, key, value, receiver);
// 如果新值和旧值不一样,则触发赋值触发器
if (hasChanged(value, oldValue)) {
trigger(target, "set", key, value, oldValue);
}
return result;
}
}
// get拦截
const get = createGetter();
// set拦截
const set = createSetter();
// 只读的get拦截
const readonlyGet = createGetter(true);
const mutableHandlers = {
get,
set,
}
const readonlyHandlers = {
get: readonlyGet,
set(target, key) {
{
// 目标对象是只读的,不能重新赋值
console.warn(`Set operation on key "${String(key)}" failed: target is readonly.`, target);
}
return true;
},
deleteProperty(target, key) {
{
// 目标对象是只读的,不能删除属性
console.warn(`Delete operation on key "${String(key)}" failed: target is readonly.`, target);
}
return true;
}
};
// 创建深层响应式对象
function reactive(target) {
return createReactiveObject(target, mutableHandlers);
}
// 创建只读的响应式对象
function readonly(target) {
return createReactiveObject(target, readonlyHandlers);
}
function createReactiveObject(target, baseHandlers) {
const proxy = new Proxy(target, baseHandlers);
return proxy;
}
const app = createApp({
setup() {
const reactiveObj = reactive({
person: {
age: 18
}
});
const readonlyObj = readonly({
name: "张三"
});
return {
reactiveObj,
readonlyObj
}
},
methods: {
addReactive() {
// 页面实时更新
this.reactiveObj.person.age += 1;
},
addReadonly() {
// 修改失败,只读对象不可变更,并导致警告
this.readonlyObj.name = "李四";
}
}
}).mount("#app");
看完3件事
1、如果文章对你有帮助,可以给博主点个赞。
2、如果你觉得文章还不错,可以动动你的小手,收藏一下。
3、如果想看更多的源码详解,可以添加关注博主。
附录:
1、Vue3.x完整版源码解析:github.com/fanqiewa/vu…
2、其它源码解析:www.fanqiewa.xyz/