effect的参数和返回值
系列文章目录
第一章 vue 简单的effect实现
第二章 链表运用到响应式中
第三章 effect问题处理
前言
上一章我们处理了effect的嵌套问题,那么这一章我们处理一下effect的参数和返回值的问题
一、effect的参数
scheduler函数
首先我们要先知道effect的参数是什么,其实就是一个对象,对象里面有一个scheduler函数,当你传入了这个函数的时候,effect的之后等到响应式数据更新的时候就会执行这个函数
我们直接上代码看一下
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><script type="module">import {effect,ref,} from "./node_modules/vue/dist/vue.esm-browser.prod.js";const name = ref("vue");// 第二部分 自定义调度器schedulereffect(() => {console.log(name.value);},{scheduler() {console.log("自定义");},});setTimeout(() => {name.value = "react";}, 1000);</script></body>
</html>
执行结果应该是先打印vue,然后在打印自定义
根据上面官方的effect能看出,当name更新之后,下次执行的是scheduler这个函数,那我们处理一下这个问题
实现scheduler
scheduler的实现比较简单,还记得我们上一章effect函数是如何实现的吗?
是用ReactiveEffect这个实例去实现的,那我们也需要去这里修改来实现
class ReactiveEffect {constructor(fn) {this.fn = fn;}run() {const prevSub = activeSub; //保存当前的effect,处理嵌套逻辑activeSub = this;console.log(prevSub?.fn);try {this.fn();} finally {// 执行完一次恢复到之前的activeSub = prevSub;}}// 通知更新调用这个函数notify() {this.scheduler();}scheduler() {this.run();}
}
function effect(fn, options) {const e = new ReactiveEffect(fn);// schedulerObject.assign(e, options);e.run();}
我们在effect函数中,合并用户传进来的对象,这里其实就是下面这个对象
{scheduler() {console.log("自定义");},}
然后我们在ReactiveEffect这个类中再新增两个方法。分别是scheduler和notify这两个方法,这个我相信大家都能看得懂吧。其实就是嵌套调用
最后我们需要修改propagate方法,为什么呢
不知道你们还记得这个方法当时我们是如何执行回调函数的?
答案是
function propagate(subs) {// 依次执行let link = subs; //保存头节点let queuedEffects = []; //保存需要执行的effectwhile (link) {// 注意:这里不能直接执行effect,因为effect可能会再次触发set,导致死循环queuedEffects.push(link.sub); //将effect保存到数组中link = link.nextSub; //指向下一个节点}// 依次执行queuedEffects.forEach((effect) => {effect.run();});
}
遍历执行effect.run(),但我们修改之后就不能执行run函数了,而是变成了effect.notify()
那最后代码就是如下显示
function propagate(subs) {// 依次执行let link = subs; //保存头节点let queuedEffects = []; //保存需要执行的effectwhile (link) {// 注意:这里不能直接执行effect,因为effect可能会再次触发set,导致死循环queuedEffects.push(link.sub); //将effect保存到数组中link = link.nextSub; //指向下一个节点}// 依次执行queuedEffects.forEach((effect) => {effect.notify(); //执行effect 2});
}
我们看一下结果
结果也是没有问题的
总结
以上是effect的参数处理方式,比较简单,大家跟着写一下基本就知道了
二、effect的返回值
返回值
老样子,我们先看下官方的返回值是什么
发现返回值其实就是run函数;
那就简单了啊,把run函数返回不就行了嘛,那好我们开始实现这块代码
function effect(fn, options) {const e = new ReactiveEffect(fn);// schedulerObject.assign(e, options);e.run();const run = e.run.bind(e);run.effect = e;return run;
}
就这么几行,但需要注意的是this的指向问题
代码
我们将目前所有的代码都贴出来,防止大家中间有什么错误遗漏的代码
effect.js代码
import { propagate, link } from "../common/sys.js";
class RefImpl {_v_isRef = true;/*** 订阅者链表头节点*/subs;/*** 订阅者链表尾节点*/subsTail;constructor(value) {this.value = value;}get value() {if (activeSub) {trackRef(this);}return this._value;}set value(newVal) {// 触发更新this._value = newVal;// this.subs?.();triggerRef(this);}
}
function ref(val) {return new RefImpl(val);
}
let activeSub = null;// interface Link {
// // effect(订阅者)
// sub: Function;
// nextSub: Link;
// prevSub: Link;
// dep: Dep; //依赖项
// }function trackRef(dep) {// 收集依赖/**TODO: 嵌套会导致外部的无法被收集 对应的index2.html* class ReactiveEffect {* constructor(fn) {* this.fn = fn;* }* run() {* activeSub = this;* try {* return this.fn();* } finally {* activeSub = undefined;* }* }* }*/if (activeSub) {link(dep, activeSub);}
}function triggerRef(dep) {if (dep.subs) {propagate(dep.subs); //触发更新}
}class ReactiveEffect {constructor(fn) {this.fn = fn;}run() {const prevSub = activeSub; //保存当前的effect,处理嵌套逻辑activeSub = this;console.log(prevSub?.fn);try {return this.fn();} finally {// 执行完一次恢复到之前的activeSub = prevSub;}}// 通知更新调用这个函数notify() {this.scheduler();}scheduler() {this.run();}
}
function effect(fn, options) {const e = new ReactiveEffect(fn);// schedulerObject.assign(e, options);e.run();const run = e.run.bind(e);run.effect = e;return run;}export { effect, ref };
sys.js
function propagate(subs) {// 依次执行let link = subs; //保存头节点let queuedEffects = []; //保存需要执行的effectwhile (link) {// 注意:这里不能直接执行effect,因为effect可能会再次触发set,导致死循环queuedEffects.push(link.sub); //将effect保存到数组中link = link.nextSub; //指向下一个节点}// 依次执行queuedEffects.forEach((effect) => {effect.notify(); //执行effect 2});
}
const link = function (dep, sub) {const newLink = {sub,nextSub: null,prevSub: null,};// 尾插法if (dep.subsTail) {dep.subsTail.nextSub = newLink;newLink.prevSub = dep.subsTail;dep.subsTail = newLink;} else {// 没有的话头尾一样dep.subs = newLink;dep.subsTail = newLink;}
};
export { propagate, link };
总结
以上就是effect参数和返回值的实现,这一章算是比较简单的,大家可以跟着手敲一下加深理解