上篇文章为了简化整体流程,视图更新直接调用的实例app的update方法,这篇文章增加依赖收集和触发视图更新过程将实例app进行解耦。
vue3.0响应式新增了副作用effect函数,结合依赖收集和视图更新完成数据响应式。
1. 副作用函数
所谓副作用函数其实就是一个函数包裹器,他用来收集要更新视图的函数,当调用effect时,会自动执行更新函数然后立即释放。代码如下:
// 副作用函数存储栈
const effectStatck = [];
// 副作用函数
function effect(fn) {
const eff = () => {
try {
// 添加待执行的函数
effectStatck.push(fn);
// 执行函数
fn();
} finally {
// 执行完释放
effectStatck.pop();
}
}
// 先执行一次
eff();
return eff;
}
基于上篇文章的代码改动如下:
2. 依赖收集
依赖收集就是利用proxy的get方法将对象的属性与副作用函数通过某种关系进行绑定,保证属性数据变化时执行对应的更新函数。
// 依赖收集
const targetMap = {};
function track(target, key) {
// 获取副作用函数
const effect = effectStatck[effectStatck.length - 1];
if (effect) {
let map = targetMap[target];
if (!map) {
map = targetMap[target] = {};
}
let deps = map[key];
if (!deps) {
deps = map[key] = [];
}
if (deps.indexOf(effect) === -1) {
deps.push(effect);
}
}
}
基于上篇文章的代码改动如下:
注:如果对象的属性类型依然是对象,要继续返回一个proxy,否则不能深度监听
3. 触发更新
当属性的数据发生变化时,会触发proxy的set执行trigger,trigger拿出依赖收集对应的副作用函数遍历执行。
// 触发副作用
function trigger(target, key) {
const map = targetMap[target];
console.log(map, targetMap);
if (map) {
const deps = map[key];
(deps || []).forEach(dep => {
dep()
});
}
}
基于上篇文章的代码改动如下:
使用Reflect为了保证更好的兼容性,一般它和proxy配对使用。
4. 实例验证
<html>
<body>
<div id="app">
</div>
<script>
// 副作用函数
const effectStatck = [];
function effect(fn) {
const eff = () => {
try {
effectStatck.push(fn);
fn();
} finally {
effectStatck.pop();
}
}
// 先执行一次
eff();
return eff;
}
// 依赖收集
const targetMap = {};
function track(target, key) {
// 获取副作用函数
const effect = effectStatck[effectStatck.length - 1];
if (effect) {
let map = targetMap[target];
if (!map) {
map = targetMap[target] = {};
}
let deps = map[key];
if (!deps) {
deps = map[key] = [];
}
if (deps.indexOf(effect) === -1) {
deps.push(effect);
}
}
}
// 触发副作用
function trigger(target, key) {
const map = targetMap[target];
console.log(map, targetMap);
if (map) {
const deps = map[key];
(deps || []).forEach(dep => {
dep()
});
}
}
const Vue = {
// 数据响应
reactive(data) {
let handler = {
// 拦截对象的属性读取
get(target, key) {
// 收集依赖
if (typeof target[key] === 'object' && typeof target[key] !== null) {
// 如果是对象,继续返回proxy,否则不能深度监听
return new Proxy(target[key], handler);
}
track(target, key);
return Reflect.get(target, key)
},
// 拦截对象的属性新增、修改
set(target, key, val) {
console.log('set');
Reflect.set(target, key, val)
// 检测到变化时通知更新,为了简化先直接修改
// app.update();
trigger(target, key);
},
// 拦截对象的属性删除
deleteProperty(target, key) {
delete target[key];
trigger(target, key);
}
}
return new Proxy(data, handler)
},
// 创建应用程序实例
createApp(options) {
return {
mount(selector) {
const parent = document.querySelector(selector);
if (!options.render) {
// 如果没有配置渲染函数,使用自定义的编译函数得到渲染函数
options.render = this.compile(parent.innerHTML);
}
//
this.setupData = options.setup();
// 给app实例增加一个更新方法,为了更好利用parent,通过变量声明函数
this.update = effect(() => {
// 通过call执行函数,并将setup的返回值作为this
const el = options.render.call(this.setupData);
// 清空宿主元素的内容
parent.innerHTML = '';
// 将el追加到宿主元素
parent.appendChild(el);
})
this.update();
},
compile(template){
// template暂时不处理
return function render() {
const div = document.createElement('div');
// 这里的this就是setup函数的返回值,可以通过解构方式获取值
let {title='', info, name='', list, listobj} = this.state;
div.innerHTML = title +'<br/>' + info.msg +'<br/>' + name + listobj[0].name;
return div
}
}
}
}
}
</script>
<script>
const {createApp, reactive} = Vue;
const app = createApp({
setup() {
const state = reactive({
title: 'hi, vue3',
// 验证是否可以深度监听
info: {
msg: 'hello'
},
listobj: [{name: 'hh', kk: {ll: '3434'}}],
});
setTimeout(() => {
// 改变属性名
state.title = '3434'
// 嵌套对象修改
state.info.msg = 'hei~~~~'
// 改变数组对象下标
state.listobj[0].name = 'change--name';
// 删除属性
delete state.title;
// 验证是否可以监听新增属性
state.name = 'you da da~~~'
}, 2000);
return {
state
}
}
});
app.mount('#app');
</script>
</body>
</html>
整个事件流转如下:
-
reactive(obj):可以监听对象属性的增删改查 -
effect(fn):可以添加副作用,组件更新函数,fn会立刻执行一次 -
fn执行时会访问属性,触发proxy的get -
通过
track(target, key)进行依赖收集(target, key和fn 关系存储) -
当改变属性时触发proxy的set,从而调用
trigger,拿出对应的fn执行
5. 总结
响应式整体流程是通过proxy的get进行依赖收集,通过map结构将要更新的函数与属性进行关联,利用proxy的set检测属性数据变化触发tirgger执行收集的对应的属性副作用包裹的函数,从而实现视图更新。
感谢点赞支持~
附上历史文章
- vue3系列:
【vue3系列】快速入门vue3,通过实例对比vue3和vue2区别
【vue3系列】一步步实现vue3.0简易版的createApp功能
【vue3系列】通过实例轻松理解vue3.0和vue2.0数据响应式原理和区别,并实现一个reactive
- webpack系列:
【webpack系列】从源码角度分析webpack打包产出及核心流程
【webpack系列】从源码角度分析loader是如何触发和执行的
【webpack系列】webpack是如何解析模块的
【webpack系列】webpack的plugin插件是如何运行的
【webpack系列】从源码角度分析webpack热更新流程
【webpack系列】从源码角度深度剖析html-webpack-plugin执行过程