当前位置: 首页 > news >正文

【学习】响应系统

响应式更新

能自动追踪依赖的技术被称为细粒度更新,是许多前端框架建立状态变化到视图变化的底层原理。

基础实现

interface Effect {execute: () => void;deps: Set<Set<Effect>>;
}type Subs = Set<Effect>;interface StackImp<T = any> {push(...elements: T[]): void; // 添加一个(或几个)新元素到栈顶;pop(): T | undefined; // 移除栈顶元素,同时返回被移除的元素;peek(): T | undefined; // 返回栈顶的元素,不对栈做任何修改;isEmpty(): boolean; // 如果栈里没有任何元素就返回true,否则返回false;clear(): void; // 移除栈里所有元素;size(): number; // 返回栈里的元素个数。
}class Stack<T = any> implements StackImp<T> {#count: number = 0;#items: { [key: number]: T } = {};push(...elements: T[]): void {for (let index = 0; index < elements.length; index++, this.#count++) {const element = elements[index];this.#items[this.#count] = element;}}pop(): T | undefined {if (this.isEmpty()) {return undefined;}this.#count--;const result = this.#items[this.#count];delete this.#items[this.#count];return result;}peek(): T | undefined {if (this.isEmpty()) {return undefined;}return this.#items[this.#count - 1];}isEmpty(): boolean {return this.#count === 0;}clear(): void {this.#items = {};this.#count = 0;}size(): number {return this.#count;}
}// 保存副作用函数和依赖栈
const effectsStack = new Stack<Effect>();// 建立订阅联系
const subscribe = (effect: Effect, subs: Subs) => {subs.add(effect);effect.deps.add(subs);
};// 清除订阅联系
const cleanup = (effect: Effect) => {// 从该effect订阅的所有state对应的subs中移除该effectfor (const subs of effect.deps) {subs.delete(effect);}// 将该effect依赖的所有state对应的subs移除effect.deps.clear();
};// 创建响应式数据
export const useState = <T extends any>(value: T
): [() => T, (newValue: T) => void] => {// 保存订阅该state变化的effectconst subs: Subs = new Set<Effect>();// 自动追踪依赖const getter = (): T => {// 获取当前上下文的effectconst effect = effectsStack.peek();if (effect) {// 建立订阅发布关系subscribe(effect, subs);}return value;};// 触发依赖const setter = (newValue: T) => {value = newValue;// 通知所有订阅该state变化的副作用函数执行for (const effect of [...subs]) {effect.execute();}};return [getter, setter];
};// 创建副作用函数和依赖
export const useEffect = (callback: () => void) => {const execute = () => {// 重置依赖cleanup(effect);// 将当前effect推入栈顶effectsStack.push(effect);try {// 执行回调callback();} catch (error) {console.log(error);} finally {// 当前effect出栈effectsStack.pop();}};const effect: Effect = {execute,deps: new Set(),};// 立刻执行一次,建立订阅发布关系execute();
};

测试用例

import { describe, test, expect } from "@jest/globals";
import { useState, useEffect } from "./reactive";describe("reactive", () => {beforeEach(() => {// 重置所有可能的全局状态(如果有)jest.clearAllMocks();});test("useState 应该正确返回 getter 和 setter 并维护状态", () => {const [getCount, setCount] = useState(0);// 初始值检查expect(getCount()).toBe(0);// 设值后检查setCount(1);expect(getCount()).toBe(1);// 多次设值验证setCount(100);expect(getCount()).toBe(100);});test("useEffect 应该在依赖变化时执行", () => {const [getCount, setCount] = useState(0);const callback = jest.fn();// 创建副作用,依赖 countuseEffect(() => {callback(getCount());});// 初始执行一次expect(callback).toHaveBeenCalledTimes(1);expect(callback).toHaveBeenCalledWith(0);// 第一次更新setCount(1);expect(callback).toHaveBeenCalledTimes(2);expect(callback).toHaveBeenCalledWith(1);// 第二次更新setCount(2);expect(callback).toHaveBeenCalledTimes(3);expect(callback).toHaveBeenCalledWith(2);});test("useEffect 应该只依赖相关状态变化", () => {const [getA, setA] = useState("a");const [getB, setB] = useState("b");const callbackA = jest.fn();const callbackB = jest.fn();// 副作用A依赖auseEffect(() => {callbackA(getA());});// 副作用B依赖buseEffect(() => {callbackB(getB());});// 初始执行expect(callbackA).toHaveBeenCalledWith("a");expect(callbackB).toHaveBeenCalledWith("b");expect(callbackA).toHaveBeenCalledTimes(1);expect(callbackB).toHaveBeenCalledTimes(1);// 更新a,只触发AsetA("a1");expect(callbackA).toHaveBeenCalledTimes(2);expect(callbackA).toHaveBeenCalledWith("a1");expect(callbackB).toHaveBeenCalledTimes(1);// 更新b,只触发BsetB("b1");expect(callbackB).toHaveBeenCalledTimes(2);expect(callbackB).toHaveBeenCalledWith("b1");expect(callbackA).toHaveBeenCalledTimes(2);});test("useEffect 应该清理旧依赖", () => {const [getFlag, setFlag] = useState(true);const [getA, setA] = useState(0);const [getB, setB] = useState(0);const callback = jest.fn();// 条件依赖:flag为true时依赖a,否则依赖buseEffect(() => {if (getFlag()) {callback(getA());} else {callback(getB());}});// 初始状态:依赖aexpect(callback).toHaveBeenCalledWith(0);callback.mockClear();// 更新a应该触发setA(1);expect(callback).toHaveBeenCalledWith(1);callback.mockClear();// 更新b不应该触发setB(1);expect(callback).not.toHaveBeenCalled();callback.mockClear();// 切换flag,此时应该依赖bsetFlag(false);expect(callback).toHaveBeenCalledWith(1); // 触发一次新的依赖收集callback.mockClear();// 现在更新a不应该触发setA(2);expect(callback).not.toHaveBeenCalled();callback.mockClear();// 更新b应该触发setB(2);expect(callback).toHaveBeenCalledWith(2);});test("多个副作用应该独立工作", () => {const [getCount, setCount] = useState(0);const effect1 = jest.fn();const effect2 = jest.fn();useEffect(() => effect1(getCount()));useEffect(() => effect2(getCount() * 2));// 初始执行expect(effect1).toHaveBeenCalledWith(0);expect(effect2).toHaveBeenCalledWith(0);// 更新后两个副作用都应执行setCount(3);expect(effect1).toHaveBeenCalledWith(3);expect(effect2).toHaveBeenCalledWith(6);expect(effect1).toHaveBeenCalledTimes(2);expect(effect2).toHaveBeenCalledTimes(2);});
});
http://www.dtcms.com/a/390489.html

相关文章:

  • Linux网络:socket网络套接字
  • 知识图谱对人工智能中自然语言处理的深层语义分析的影响与启示
  • 从车间到云端:Kepware如何加速IIoT落地
  • MyISAM 与 InnoDB 深度对比:如何正确选择 MySQL 存储引擎
  • 串口无线数传模块实现化工园区与3公里外水泵PLC无线通讯实现设备数据传输
  • rook-ceph自定义添加osd流程
  • 【需求导向】解读660页智慧教育大数据信息化顶层设计及智慧应用建设方案
  • InnoDB 引擎深潜指南---从逻辑结构到 MVCC 原理,带源码级案例与性能要点
  • Android Compose 开发 界面间的跳转(Router)
  • unity(C#/cs)请求 python django后端服务器预制体渲染 scroll list 视频列表
  • 《Linux 指令实战进阶:从终端新手到 shell 驾驭者的技术跃迁(第三篇)》
  • 临床AI产品化全流程研究:环境聆听、在环校验与可追溯系统的多技术融合实践(下)
  • Croe 11.0 学习笔记:1.5 草绘
  • Hadoop 1.x 与 2.x 版本对比:架构演进与核心差异解析
  • 【5/20】Express.js 基础:构建 RESTful API,实现用户数据端点
  • SmartX 榫卯企业云平台+ StarRocks 大数据联合解决方案
  • CodeX 新王已来:从技术原理到工程实践,AI 如何重构编程全流程
  • 智慧赋能:King‘s Biobank 重构生物样本管理新范式
  • Chromium 138 编译指南 Ubuntu 篇:环境配置与基础准备(一)
  • 知识库新增三方应用AI问答,新增标签管理,集成Excalidraw,重构全文检索,zyplayer-doc 2.5.4 发布啦!
  • JupyterLab部署及使用
  • 本地连接服务器使用jupyter
  • 泰迪智能科技分享数据挖掘定义、主要方法、预处理、应用领域
  • (vue)vue2实现导入excel文件功能
  • 【C语言数据结构】第1章:绪论
  • Python自动化办公2.0全能实战:从Excel到BI大屏,从OCR到机器学习,一站式提升办公效率100倍
  • 第十四届蓝桥杯青少组C++选拔赛[2022.11.27]第二部分编程题(3、业务办理时间)
  • 微服务-网关gateway理论与实战
  • 吴恩达机器学习笔记week1-2(线性回归模型及Sklearn的使用)
  • 11.2.4 聊天记录拉取设计与实现