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

react.js 手写响应式 reactive

Redux 太繁琐,Mbox 很酷但我们可能没必要引入新的包,那就让我们亲自在 react.js 中通过代理实现一套钩子来达到类似 vue 的响应式状态:

实现 reactive hooks

代理类声明

代理状态的类应当提供可访问的状态,和订阅变化的接口。

export type Listener<T> = (state: T) => any;export interface ReactiveCtxModel<T = any> {value: T;subscribe(listener: Listener<T>): () => void;
}

代理类实现

使用 es6 class 来实现代理类,es6 class 提供了属性的 get/set 访问器,让我们在通过 obj.key 的方式访问时不是直接访问,而是经过了代理,真实的值则通过 private 被设置为私有属性。

类的构造器,我们传入用 React.useState 获得的返回,没错,想在 react 中让页面响应数据的变化,我们仍然需要 useState,不传 setState 的话,这个 Reactive 将是惰性的,因为他无法触发页面的重渲染。

私有属性除了要保存的 state,还有 listeners 数组来保存监听变化要触发的函数,这些函数在 state 每次被 set 访问器调用时跟着调用。

export class Reactive<T = any> implements ReactiveCtxModel {private _state: T;private _setState: any = (newState: T) => {this._state = newState;};private _listeners: Listener<Readonly<T>>[] = [];constructor(state: T, setState?: any) {this._state = state;setState ? (this._setState = setState) : void 0;}get value(): T {return this._state;}set value(newState: T) {this._setState?.(newState);this._listeners.forEach((listener) => listener(newState));}subscribe(listener: Listener<T>) {this._listeners.push(listener);return () => {this._listeners = this._listeners.filter((l) => l !== listener);};}static isReactive(obj: any) {return Reactive.prototype.isPrototypeOf(obj);}
}

实现创建代理的钩子函数

每次在代码里手动创建 useState() 然后还要 new Reactive() 太麻烦了,我们将这几个操作封装成一个 hook Reactify,然后再赋给 reactive,这样我们就可以直接使用 reactive(initialValue) 创建响应式对象。(为什么要先创建 Reactify?因为 react 约定 react 的 use 钩子的顶部空间应当命名为 useXXX 或者是 大写字母 开头,因为我喜欢 reactive 这个名字,所以做了一个交换)

const Reactify = <T = any>(initialValue: T): Reactive<T> => {const [state, setState] = React.useState<T>(initialValue);const observer = new Reactive(state, setState);return observer;
};
/*** reactive is same with Reactify*/
export const reactive = Reactify;

example:

const Demo: React.FC = () => {let state = reactive(0);const num = state.value;return (<><ButtononClick={() => {state.value = state.value + 1;}}>{num}</Button></>);
};

实现监听函数

直接在 Reactive 对象上调用 subscribe 很棒,但有时候我更喜欢这个操作可以抽出来,于是有了下面这个 listen 函数,传入要监听的 Reactive 对象,接着在 then 中链式传入要触发的回调,观感上更优雅。

/*** When store.state changes, call the given function.* @param target listened Reactive store* @returns unlistener*/
export function listen<T = any>(target: Omit<Reactive<T>, "_state" | "_setState">) {return {then: (...fns: ((value: T) => any)[]) => {const fn = (value: T) => fns.forEach((f) => f(value));const dispose = target.subscribe(fn);return dispose;},};
}

example:

  listen(obj).then((newVal) => {console.log(`newVal: ${newVal}`);});

借助 Context 传递 Reactive

以上的 reactive 只能在单组件局部使用,即使通过 props 传递给子组件,子组件也只有只读的权利。如果需要跨组件共享 Reactive 代理,我们可以借助 React.Context:

创建默认 Context

import { createContext } from "react";
import { Listener, Reactive } from "./model";export const createReactiveContext = <T = any>(initialValue?: T) => {const reactiveObject = new Reactive(initialValue);return createContext<ReactiveCtxModel<T> | undefined>(reactiveObject as any);
};const ReactiveCtx = createReactiveContext();export default ReactiveCtx;

实现 useReactive 钩子

useReactive 可以接收一个初值,如果得到了初值就开辟一个新的 context 和 Reactive 对象,否则延用上一步创建的 ReactiveCtx。

/*** Accept a value and return a reactive object. When initalValue is valid a new reactive object will be created.*/
export const useReactive = <T = any>(initialValue?: T): Reactive<T> => {const [state, setState] = React.useState<T>(initialValue ?? (undefined as T));const reactiveObj = new Reactive(state, setState);const defaultContextModel = React.useContext((initialValue as any) ?? ReactiveCtx);if (initialValue !== undefined && initialValue !== null) {return reactiveObj as Reactive<T>;}return defaultContextModel as Reactive<T>;
};

实现 useReactiveContext 钩子

useReactive 接收初值后新建的 context 不能为其它组件获取,要让其它组件共享非默认的 context,我们就需要在外部额外创建并导出新的 context,并实现一个 useReactiveContext 钩子来接收新的context,这样就可以共享新的 context,同样如果没有传入新的 context,我们将沿用默认的 ReactiveCtx。

export const useReativeContext = <T = any>(context?: React.Context<ReactiveCtxModel<T> | undefined>): Reactive<T> => {const reactiveCtxModel = React.useContext(context || ReactiveCtx);return reactiveCtxModel as Reactive<T>;
};

现在,我们将原先 demo 中使用的 raective 替换为 useReactive,然后我们即可自由的跨组件共享 Reactive。
example:

const Demo: React.FC = () => {let state = useReactive(0);const num = state.value;listen(state).then((newVal) => {console(`newVal: ${newVal}`);});return (<><Button$click={() => {state.value = state.value + 1;}}>{num}</Button><ReactiveCtx.Provider value={state}><Kid /></ReactiveCtx.Provider></>);
};

Kid:

function Kid() {const state = useReactive<number>();return (<><Taglightstyle={{ cursor: "pointer" }}onClick={() => {state.value++;}}>state : {state.value}</Tag><Taglightstyle={{ cursor: "pointer" }}onClick={() => {state2.value++;}}>state2 : {state2.value}</Tag><context.Provider value={state2}><KidKid /></context.Provider></>);
}

Bingo! 到这里我们就基本实现了 reactive 啦,拜拜~

相关文章:

react.js 手写响应式 reactive

Redux 太繁琐&#xff0c;Mbox 很酷但我们可能没必要引入新的包&#xff0c;那就让我们亲自在 react.js 中通过代理实现一套钩子来达到类似 vue 的响应式状态&#xff1a; 实现 reactive hooks 代理类声明 代理状态的类应当提供可访问的状态&#xff0c;和订阅变化的接口。 …...

代码随想录打卡第四十六天|完全背包 ● 518. 零钱兑换 II ● 377. 组合总和 Ⅳ

完全背包理论 有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品都有无限个&#xff08;也就是可以放入背包多次&#xff09;&#xff0c;求解将哪些物品装入背包里物品价值总和最大。 完全背包和01背包问题唯一…...

【BP-Adaboost预测】基于BP神经网络的Adaboost的单维时间序列预测研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

Origami Studio for Mac:塑造未来,掌握原型设计之巅

在当今高度竞争的设计领域&#xff0c;原型设计的重要性不言而喻。它不仅是沟通想法&#xff0c;也是测试和改进设计的关键环节。而现在&#xff0c;一款强大的原型设计工具——Origami Studio for Mac&#xff0c;正在席卷设计界&#xff0c;以其独特的功能和卓越的性能&#…...

UML类图中各箭头表示总结

UML类图中各箭头表示总结 1、泛化2、实现3、依赖4、关联5、聚合6、组合 在UML类图中&#xff0c;箭头关系是用来表示类之间的关系的。箭头关系的种类有以下几种&#xff1a; 1、泛化 泛化&#xff1a;表示类之间的继承关系。箭头从子类指向父类。箭头&#xff1a;实线空心三角…...

神经网络量化----为了部署而特别设计

引言:一般神经网络量化有两个目的: 为了加速,在某些平台上浮点数计算比较耗费时间,替换为整形可以加快运算为了部署,某些平台上只支持整形运算,比如在芯片中如果是第1个目的,则使用常规的量化手段就可以满足,将浮点数运算变成整形运算+较少的浮点运算 但是如果是第2个目…...

代码随想录算法训练营Day60|单调栈01

代码随想录算法训练营Day60|单调栈01 文章目录 代码随想录算法训练营Day60|单调栈01一、739. 每日温度二、496.下一个更大元素 I 一、739. 每日温度 class Solution {public int[] dailyTemperatures(int[] temperatures) {//单调栈int lenstemperatures.length;int result[]n…...

openMP学习笔记 -编程模型

OpenMP模型 gcc编译openmp指令&#xff1a;gcc test.cpp -o test -fopenmp 定积分计算 函数面积 给定一个定积分&#xff0c;计算其面积&#xff1a; ∫ 0 1 4.0 ( 1 x 2 ) d x \int^{1}_{0}{\frac{4.0}{(1x^2)}dx} ∫01​(1x2)4.0​dx omp 概念 并行区域 并行区域用于…...

【Hive SQL 每日一题】环比增长率、环比增长率、复合增长率

文章目录 环比增长率同比增长率复合增长率测试数据需求说明需求实现 环比增长率 环比增长率是指两个相邻时段之间某种指标的增长率。通常来说&#xff0c;环比增长率是比较两个连续时间段内某项数据的增长量大小的百分比。 环比增长率反映了两个相邻时间段内某种经济指标的变…...

Java设计模式之外观模式(Facade Pattern)

外观模式&#xff08;Facade Pattern&#xff09;是一种结构型设计模式&#xff0c;它提供了一个统一的接口&#xff0c;用于访问子系统中的一组接口。外观模式通过隐藏子系统的复杂性&#xff0c;简化了客户端与子系统之间的交互&#xff0c;提供了一个更简单、更直观的接口。…...

【大疆智图】大疆智图(DJI Terra 3.0.0)安装及使用教程

大疆智图是一款以二维正射影像与三维模型重建为主的软件,同时提供二维多光谱重建、激光雷达点云处理、精细化巡检等功能。它能够将无人机采集的数据可视化,实时生成高精度、高质量三维模型,满足事故现场、工程监测、电力巡线等场景的展示与精确测量需求。 文章目录 1. 安装D…...

腾讯地图基本使用(撒点位,点位点击,弹框等...功能) 搭配Vue3

腾讯地图的基础注册账号 展示地图等基础功能在专栏的上一篇内容 大家有兴趣可以去看一看 今天说的是腾讯地图的在稍微一点的基础操作 话不多说 直接上代码 var marker ref(null) var map var center ref(null) // 地图初始化 const initMap () > {//定义地图中心点坐标…...

散列表:Word文档中的单词拼写检查功能是如何实现的?

文章来源于极客时间前google工程师−王争专栏。 一旦我们在Word里输入一个错误的英文单词&#xff0c;它就会用标红的方式提示“编写错误”。Word的这个单词拼写检查功能&#xff0c;虽然很小但却非常实用。这个功能是如何实现的&#xff1f; 散列别&#xff08;Hash Table&am…...

智慧公厕蜕变多功能城市智慧驿站公厕的创新

随着城市发展的不断推进&#xff0c;对公共设施的便利性和智能化要求也日益提高。为满足市民对高品质、便捷、舒适的公共厕所的需求&#xff0c;智慧公厕行业的领航厂家广州中期科技有限公司&#xff0c;全新推出了一体化智慧公厕驿站。凭借着“高科技碳中和物联网创意设计新经…...

R语言清洗与处理数据常用代码段

去掉数据框df的某一列&#xff1a; # 删除不必要的变量 data$unnecessary_var <- NULL 选择需要的列进行读入数据框&#xff1a; # 选择需要的列 selected_cols <- c("col1", "col2", "col3") data <- fread("data.csv", s…...

centos 7.9 安装python 3.10的tls问题,

本地开发升级成了py3.10.6,服务器测试时安装py3.10.4 发现无法正常使用pip3 pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available. 印象中py3的高版本依赖高版本的openssl,centos 7下默认的openssl为1.0.x, 问题很简…...

pytorch,tf维度理解RNN

input_t input_t.squeeze(1) 这行代码用于从 input_t 中去除尺寸为1的维度。在深度学习中&#xff0c;经常会出现具有额外尺寸为1的维度&#xff0c;这些维度通常是为了匹配模型的期望输入维度而添加的。 在这里&#xff0c;input_t可能具有形状 (batch_size, 1, feature_dim…...

算法刷题-数组

算法刷题 209. 长度最小的子数组-二分或者滑动窗口 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组 [numsl, numsl1, ..., numsr-1, numsr] &#xff0c;并返回其长度**。**如果不存在符合条件的子数…...

可视化数学分析软件 MATLAB R2021b mac中文版软件介绍

MATLAB R2021b mac作为数学类科技应用软件中首屈一指的商业数学软件&#xff0c;可以帮助您进行矩阵运算、绘制函数和数据、实现算法、创建用户界面、连接其他编程语言的程序等,主要应用于工程计算、控制设计、信号处理与通讯、图像处理、信号检测、金融建模设计与分析等领域。…...

罗技摄像头左右翻转

需要下载驱动lws&#xff08;我的是c310&#xff09; LWS 罗技摄像头驱动下载 打开驱动程序&#xff0c;高级设置。有个镜像。...

sqlserver 根据指定字符 解析拼接字符串

DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)

宇树机器人多姿态起立控制强化学习框架论文解析 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架&#xff08;一&#xff09; 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...

JDK 17 新特性

#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持&#xff0c;不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的&#xff…...

2025季度云服务器排行榜

在全球云服务器市场&#xff0c;各厂商的排名和地位并非一成不变&#xff0c;而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势&#xff0c;对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析&#xff1a; 一、全球“三巨头”…...

MySQL 部分重点知识篇

一、数据库对象 1. 主键 定义 &#xff1a;主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 &#xff1a;确保数据的完整性&#xff0c;便于数据的查询和管理。 示例 &#xff1a;在学生信息表中&#xff0c;学号可以作为主键&#xff…...

Python训练营-Day26-函数专题1:函数定义与参数

题目1&#xff1a;计算圆的面积 任务&#xff1a; 编写一个名为 calculate_circle_area 的函数&#xff0c;该函数接收圆的半径 radius 作为参数&#xff0c;并返回圆的面积。圆的面积 π * radius (可以使用 math.pi 作为 π 的值)要求&#xff1a;函数接收一个位置参数 radi…...

ui框架-文件列表展示

ui框架-文件列表展示 介绍 UI框架的文件列表展示组件&#xff0c;可以展示文件夹&#xff0c;支持列表展示和图标展示模式。组件提供了丰富的功能和可配置选项&#xff0c;适用于文件管理、文件上传等场景。 功能特性 支持列表模式和网格模式的切换展示支持文件和文件夹的层…...

Vue3中的computer和watch

computed的写法 在页面中 <div>{{ calcNumber }}</div>script中 写法1 常用 import { computed, ref } from vue; let price ref(100);const priceAdd () > { //函数方法 price 1price.value ; }//计算属性 let calcNumber computed(() > {return ${p…...

Gitlab + Jenkins 实现 CICD

CICD 是持续集成&#xff08;Continuous Integration, CI&#xff09;和持续交付/部署&#xff08;Continuous Delivery/Deployment, CD&#xff09;的缩写&#xff0c;是现代软件开发中的一种自动化流程实践。下面介绍 Web 项目如何在代码提交到 Gitlab 后&#xff0c;自动发布…...

Vuex:Vue.js 应用程序的状态管理模式

什么是Vuex&#xff1f; Vuex 是专门为 Vue.js 应用程序开发的状态管理模式 库。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。 在大型单页应用中&#xff0c;当多个组件共享状态时&#xff0c;简单的单向数据流…...