项目地址:gitee.com/luobf22/vue…
概述
- 本文旨在研究Vue3双向绑定的源码,虽然只是简单的案例,但是优化了Vue2对于数组和对象操作的缺陷
- 如果有不同观点可以评论或者自行查看源码
vue3和vue2的异同
- vue2是通过
Object.defineProperty数据劫持,vue3是通过Proxy代理
- vue2、vue3都是通过get获取数据、set修改数据
- vue2通过下标修改数组,添加obj属性时,Object.defineProperty不经过set,只能经过get,而proxy经过set
- vue2会对每个属性赋予
watch对象,然后通过observe观察属性的变化,然后进行update,而vue3通过track收集依赖,数据修改的时候,trigger触发依赖,依赖表里的属性修改的时候,相应修改,并且effect触发相应的副作用函数,将属性相关的函数,属性,dom相应修改
完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="app">
<input type="text" id="inputElement">
<p id="textElement">{{state.message}}</p>
<button id="addBtn">给obj添加属性</button>
<button id="changeBtn">修改obj属性</button>
<p id="objElm">{{obj}}</p>
<button id="changeArr">给arr修改所有参数</button>
<button id="changeIndexArr">修改obj某个下标参数</button>
<p id="arrElm">{{arr}}</p>
</div>
</body>
<script src="./手写vue3.js"></script>
</html>
const effects = [];
const targetMap = new Map();
let activeEffect;
function effect(fn) {
const effectFn = () => {
activeEffect = effectFn;
fn();
};
effectFn();
return effectFn;
}
function track(target, key) {
if (activeEffect) {
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
let dep = depsMap.get(key);
if (!dep) {
dep = new Set();
depsMap.set(key, dep);
}
dep.add(activeEffect);
}
}
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (depsMap) {
const dep = depsMap.get(key);
if (dep) {
dep.forEach(effect => effect());
}
}
}
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
track(target, key);
const value = Reflect.get(target, key, receiver);
if (typeof value === 'object' && value !== null) {
return reactive(value);
}
return value;
},
set(target, key, value, receiver) {
const hadKey = key in target;
const result = Reflect.set(target, key, value, receiver);
if (!hadKey) {
track(target, key);
trigger(target, key);
}
if (hadKey || (value !== target[key])) {
trigger(target, key);
}
return result;
}
});
}
function render() {
const inputElement = document.getElementById('inputElement');
const textElement = document.getElementById('textElement');
const addBtn = document.getElementById('addBtn');
const changeBtn = document.getElementById('changeBtn');
const objElm = document.getElementById('objElm');
const changeArr = document.getElementById('changeArr');
const changeIndexArr = document.getElementById('changeIndexArr');
const arrElm = document.getElementById('arrElm');
const state = reactive({
message: 'Initial Message',
arr: [1, 2, 3, 4, 5],
obj: {
name: 'luo',
year: 27
}
});
inputElement.addEventListener('input', (e) => {
state.message = e.target.value;
});
addBtn.addEventListener('click', (e) => {
state.obj.sex = '男'
state.obj.age = 27
});
changeBtn.addEventListener('click', (e) => {
state.obj.name = 'lbf'
});
changeArr.addEventListener('click', (e) => {
state.arr = [1, 2, 3]
});
changeIndexArr.addEventListener('click', (e) => {
state.arr[0] = 9
});
effect(() => {
textElement.textContent = state.message;
objElm.textContent = JSON.stringify(state.obj);
arrElm.textContent = state.arr;
});
}
render();
代码解析
-
reactive
- 返回一个proxy对象,对创建的对象进行代理
- get,将属性名key传递给track函数进行依赖收集,
const value = Reflect.get(target, key, receiver)value是key对应的值,如果value是对象,则回调reactive(value),将value对象下的属性也进行依赖收集,如果value是对象,但不回调reactive(value),则会造成修改数组下标的属性时无法及时更新视图,因为对象下的属性没有一一进行依赖收集
- set,判断修改的属性值是不是在原有的依赖表里,如果不在,则通过track进行依赖收集更新,如果在则trigger触发依赖,实现数据的变更,以及通过effect进行视图的变更
-
track
-
trigger
- 触发依赖,并执行effect,副作用函数触发,更新视图
-
effect
- 副作用函数,将与属性变更相关的fn执行,更新视图,如果出现依赖无法及时更新,可以尝试// activeEffect = null;将此代码注释,因为如果activeEffect为空,无法将新的属性进行依赖更新