基于React Hooks的简单全局状态共享实现方案

news2024/11/15 7:18:38

什么是全局状态共享?

  常规B端项目中往往需要一些全局通用的状态来实现跨组件层级甚至整个系统全局的共享,比如:当前用户的信息、某些业务状态等。

都有什么解决方案?

  业内常规的成熟方案一般有:mobxredux等专门的全局状态管理库,相对而言基本可以覆盖支持所有的复杂业务场景,再也就简单粗暴通过多个Context嵌套来实现共享。

  多层Context嵌套这种‘太粗暴’的方案基本上就不要考虑了qwq,但对于一些状态逻辑相对单一且通过服务端获取以后不需要长链路复杂处理共享的状态来讲其实是引入专门的库是没有太大必要的。拿redux为例,在react中使用需要引入react-redux来配合处理异步状态还需要redux-thunk或者redux-saga等类似中间件,然后自己定义和管理actionreducer的处理逻辑,整体来讲是比较繁重的。(当然redux中间件的洋葱模型设计一定程度上为用户提供了规范的自定义通用状态处理逻辑的方案是非常优秀的,尤其在复杂业务状态数据处理场景上)。

我们可以怎么做?

  但是嘞~当我们状态很简单,比如我就要存一下当前用户信息,跨组件全局状态联动很少等等(很多情况就是一个地方初始化某个状态以后就在各个地方读就好了)没有其他操作,而恰恰我们的项目又是支持React Hooks的,那我们就有了简单是实现方案。本质全局状态存储的原理还是基于React自带的Context来实现的。

图片.png

  首先我们要知道的是,当通过Context做全局状态存储时,一旦其value改变,那么对应依赖其value值的组件将重新的渲染,所以当我们使用同一个Context存储所有全局状态时,就不得不考虑来避免更新某个值而造成所有依赖其value组件重新渲染的扯淡行为。由此我们便需要保证value值或者其引用地址不变。便可以借助useRef来实现。

import React, { useRef } from 'react';
interface PropsType {}

export const GlobalStoreContext = React.createContext<{

[x: string]: any

}>({});

const GlobalStore: React.FC<PropsType> = (props) => {

/**

* 通过ref保证同一份引用,使context的value不变,避免由context value改变导致所有依赖组件更新

*/
const contextValue = useRef<any>({});

return (

        <GlobalStoreContext.Provider
            value={contextValue.current}
        >
            {props.children}
        </GlobalStoreContext.Provider>
    );
};

export default GlobalStore;

  避免更新解决了,但是现在彻底不更新了,然后要考虑怎么做到按需触发相关组件触发。问题便成为了常规的按需触发某些回调的更新机制,由此便可以考虑到发布-订阅模式(redux也是基于类似原理实现的)。同时借助useReducer来实现数据定义及更新。考虑到对象的某个值改变同时要生成一个新的对象来触发’中心发布’引入了immutable。

import React, { useLayoutEffect, useReducer, useRef } from 'react';
import Store from './Store';
import {fromJS} from 'immutable'

interface PropsType {}

+ const initialState = fromJS({});

+ function reducer(state: Immutable.Map<string, any>, action: {key: string, value: any}) {
+     if (action?.key) {
+         return state.set(action.key, action.value);
+     }
+     return state;
+ }

export const GlobalStoreContext = React.createContext<{
    state: Immutable.Map<string, any>,
    dispatch: React.Dispatch<{
        key: string;
        value: any;
    }>,
    listeners?: Set<(v: {[x: string]: any}) => void>,
    [x: string]: any
}>({
    state: fromJS({}),
    dispatch: () => {}
});

const GlobalStore: React.FC<PropsType> = (props) => {

+   const [state, dispatch] = useReducer(reducer, initialState);

    /**
    * 通过ref保证同一份引用,使context的value不变,避免由context value改变导致所有依赖组件更新
    */
    const contextValue = useRef<any>({
+     state,
+     dispatch: dispatch || (() => {}),
    // 订阅回调,Set结构避免重复订阅
+     listeners: new Set()
    });

    // '订阅中心-处理发布'
+     useLayoutEffect(() => {
+       contextValue.current.state = state;
+       contextValue.current?.listeners?.forEach((l: (v: any) => void) => l(state));
+     }, [state]);
    return (
        <GlobalStoreContext.Provider
            value={contextValue.current}
        >
            {props.children}
        </GlobalStoreContext.Provider>
    );
};

export default GlobalStore;

  至此,数据存储更新的功能有了,中心发布能力也具备了,还差对外提供状态值以及订阅能力。这一层可以借助自定义的hooks来实现,通过useState来做依赖业务组件的状态中间存储,来触发业务组件的更新,为了避免无效的更新我们通过Object.is来判断值的更新情况。通过useLayoutEffect来做初始化的订阅工作。

import { useCallback, useContext, useLayoutEffect, useState } from 'react';
import { GlobalStoreContext } from '.';

/**
*
* @param 监听:key
* @returns
*/
export const useStore = (key: string) => {
    const { state, dispatch, listeners } = useContext(GlobalStoreContext);
    const [value, setValue] = useState<any>(state.get(key));

    const listener = useCallback((state: {[x: string]: any}) => {
        // 判断是否需要更新
        if(!Object.is(value, state.get(key))) {
        setValue(state.get(key));
        }
    }, [value])

    useLayoutEffect(() => {
        // console.log('添加 listener', key);
        listeners?.add(listener);
        // 添加监听时先执行一遍获取当前状态值
        listener(state);
        
        return () => {
            // console.log('移除 listener', key);
            listeners?.delete(listener)
        }
    }, [listener])

    const updateValue = useCallback(
        (v: any) => {
            dispatch({
                key,
                value: v,
            });
        },
        [key, dispatch],
    );

    return [value, updateValue];
};

/**
* 本着专一的原则,获取单次更新但各个值的更新方法,
* 当然也可以根据自己的习惯,通过变通 reducer的处理逻辑
* 自己定义dispatch逻辑
* @returns
*/

export const useStoreDispatch = () => {
    const { dispatch } = useContext(GlobalStoreContext);

    const update = useCallback(
        (key: string, v: any) => {
            dispatch({
                key,
                value: v,
            });
        },
        [dispatch],
    );

    return [update];
};

  100多行代码,一个简单的全局状态管理机制就实现了。

要怎么用?

   东西有了,用起来就比较简单了。

wrap.tsx

import React from 'react';
import Store from './store';

interface PropsType {}

const Wrap: React.FC<PropsType> = props => {
    return (
        <GlobalStore>
            <Store></Store>
        </GlobalStore>
    )
}

export default Wrap;

store.tsx

import React, { useEffect } from 'react';
import { useStoreDispatch } from './use-store';
import Detail1 from '../detail';
import Detail2 from '../detail2';

interface PropsType {}

/**
* 这一层可用于初始化全局数据
* @param props
* @returns
*/

const Store: React.FC<PropsType> = (props) => {
    const [dispatchStore] = useStoreDispatch();

    useEffect(() => {
        dispatchStore('a', 111111);
        dispatchStore('b', 222222);

        setTimeout(() => {
            dispatchStore('a', 'aaaaaaa');
        }, 5000);

        setTimeout(() => {
            dispatchStore('b', 'bbbbb');
        }, 7000);

        setTimeout(() => {
            dispatchStore('a', 'aaaaaaa111');
        }, 10000);
    }, [ dispatchStore]);

    return (
        <>
            <Detail1/>
            <Detail2/>
        </>
    );
};

export default Store;

detail.tsx

import { useStore } from "@/components/global-store/use-store";

function Detail1() {

    const [a, updateA] = useStore('a');

    return (
        <div>
        {
            (() => {
                console.log('aaaaaa');
                return (
                    <h1>{a}</h1>
                )
            })()
        }
        </div>
    )
}

export default Detail1;

detail2.tsx

import { useStore } from "@/components/global-store/use-store";

function Detail1() {
    const [b] = useStore('b');

    return (
        <div>
        {
            (() => {
                console.log('bbbbbbb');
                return (
                    <h1>{b}</h1>
                )
            })()
        }
        </div>
    )
}

export default Detail1;

执行结果记录:
图片.png

END…

图片.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/393196.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

containerd启动过程

github: https://github.com/containerd/containerd 1. 前言 dockerd 是 docker engine 守护进程&#xff0c;dockerd 启动时会启动 containerd 子进程&#xff0c;dockerd 与 containerd 通过 rpc 进行通信ctr 是 containerd 的 clicontainerd 通过 shim 操作 runc&#xff0…

throw语句以及throw “error“ 和 throw new Error(“error“)的区别

文章目录什么是 throw 语句&#xff1f;throw 语句后面跟一个对象Error 构造函数对象结论什么是 throw 语句&#xff1f; throw 语句用来抛出一个用户自定义的异常。当前函数的执行将被停止&#xff08;throw 之后的语句将不会执行&#xff09;&#xff0c;并且控制将被传递到调…

AOP的另类用法 (权限校验自定义注解)

&#x1f473;我亲爱的各位大佬们好&#x1f618;&#x1f618;&#x1f618; ♨️本篇文章记录的为 AOP的另类用法 (权限校验&&自定义注解) 相关内容&#xff0c;适合在学Java的小白,帮助新手快速上手,也适合复习中&#xff0c;面试中的大佬&#x1f649;&#x1f649…

磁盘存储和文件系统管理

磁盘存储和文件系统管理1. 磁盘结构1.1设备文件1. 设备类型&#xff1a;2. 磁盘设备的设备文件命名&#xff1a;3. 虚拟磁盘&#xff1a;4. 不同磁盘标识&#xff1a;a-z,aa,ab…5. 同一设备上的不同分区&#xff1a;1,2, ...6. 创建设备文件7. 工具 dd常用选项示例demo8. hexd…

maven的安装配置

目录 1. Maven的安装配置 1.1检测jdk的版本 1.2下载maven 1.3配置maven环境变量 2.认识maven的目录结构 2.1 创建一个文件夹作为项目的根目录 1.创建如下结构的目录 2. 在pom.xml文件中写入如下内容(不用记忆) 3.在mian-->java--》下边创建java文件​编辑 4.cmd下…

XGboost部分细节补充

XGBoost算法原理详解与参数详解 R语言XGBoost参数详解 XGBoost部分细节补充1. XGBoost线性模型的实现2.XGBoost对二分类和多分类的处理方法1. XGBoost线性模型的实现 前面文章中已经详细的介绍了XGBoost基于决策树的实现&#xff0c;今天我们主要介绍XGBoost基于线性模型的实现…

模式识别 —— 第二章 参数估计

模式识别 —— 第二章 参数估计 文章目录模式识别 —— 第二章 参数估计最大似然估计&#xff08;MLE&#xff09;最大后验概率估计&#xff08;MAP&#xff09;贝叶斯估计最大似然估计&#xff08;MLE&#xff09; 在语言上&#xff1a; 似然&#xff08;likelihood&#xf…

【Git】P1 Git 基础

Git 基础Git 基本概念集中式版本控制工具 与 分布式版本控制工具Git 下载与安装Bash 初始设置创建本地仓库Git 三区概念一个简单的提交流程更改文件后再次提交git 实现版本切换查看提交日志设置 git 快捷键版本切换&#xff08;一&#xff09;版本切换&#xff08;二&#xff0…

华为OD机试题,用 Java 解【数据分类】问题

华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典使用说明 参加华为od机试,一定要注意不…

0104路径搜索和单点路径-无向图-数据结构和算法(Java)

文章目录2 单点路径2.1 API2.2 算法实现后记2 单点路径 单点路径。给定一幅图和一个起点s&#xff0c;回答“从s到给定的目的顶点v是否存在一条路径&#xff1f;如果有&#xff0c;找出这条路径。”等类似问题。 2.1 API 单点路径问题在图的处理邻域中十分重要。根据标准设计…

PHP, Python和Java的区别

PHP, Python和Java是广泛使用的编程语言。每种编程语言都有其独特的优点和缺点。在本文中&#xff0c;我们将对这些编程语言进行分析&#xff0c;并探讨它们在不同应用场景中的最佳用途。一、PHPPHP是一种广泛使用的Web编程语言&#xff0c;它可以在服务器上运行&#xff0c;并…

程设 | week2:STL

&#x1f4da;回顾C &#x1f407;struct和class 从功能上说&#xff0c;struct和class几乎没什么区别在不显式声明的情况下&#xff0c;struct成员默认为public&#xff0c;class默认为private和c语言的struct不同&#xff0c;c的struct可以定义成员函数&#xff0c;重载运算…

G6绘制树形图(自定义节点、自定义边、自定义布局)

目录1 设计节点1.1 定义节点和文本1.2 增加节点1.3 自定义节点样式2 树图配置2.1 允许使用自定义dom节点2.2 内置行为自定义边layout布局demo1 设计节点 在 registerNode 中定义所有的节点 G6.registerNode(tree-node, {drawShape: function drawShape(cfg, group) {定义图中…

aws appconfig 理解和使用appconfig对应用程序进行动态配置

参考资料 Automating Feature Release using AWS AppConfig Integration with AWS CodepipelineDeploying application configuration to serverless: Introducing the AWS AppConfig Lambda extensionCreate a pipeline that uses Amazon AppConfig as a deployment provider…

秒懂算法 | 搜索基础

本篇介绍了BFS和DFS的概念、性质、模板代码。 01、搜索简介 搜索,就是查找解空间,它是“暴力法”算法思想的具体实现。 暴力法(Brute force,又译为蛮力法):把所有可能的情况都罗列出来,然后逐一检查,从中找到答案。这种方法简单、直接,不玩花样,利用了计算机强大的…

JavaScript 中的 String 类型 模板字面量定义字符串

ECMAScript 6新增了使用模板字面量定义字符串的能力。与使用单引号或双引号不同&#xff0c;模板字面量保留换行字符&#xff0c;可以跨行定义字符串&#xff1a; let str1 早起的年轻人\n喜欢经常跳步;let str2 早起的年轻人喜欢经常跳步;console.log(str1);// 早起的年轻人…

【游戏逆向】游戏玩家技能冷却分析

技能冷却对于不同的游戏有不同的存放方式,而技能冷却的遍历也大多不会和技能的普通属性放在一起,在《在**明月刀》这款游戏中,技能的冷却判断格外重要,因为技能的连贯性对打怪的效率影响很大。 我们需要找一个冷却相对较长的技能用来进行扫描和过滤,一般选择几十秒即可,…

【本周特惠课程】基于GAN的图像增强理论与实践(涵盖图像降噪、色调映射、去模糊、超分辨、修复等方向)...

前言欢迎大家关注有三AI的视频课程系列&#xff0c;我们的视频课程系列共分为5层境界&#xff0c;内容和学习路线图如下&#xff1a;第1层&#xff1a;掌握学习算法必要的预备知识&#xff0c;包括Python编程&#xff0c;深度学习基础&#xff0c;数据使用&#xff0c;框架使用…

Windows扫描工具RunScanner使用实验(21)

实验目的 掌握利用Runscanner扫描和分析电脑&#xff1b;预备知识 RunScanner是一个完全免费的Windows系统工具,您可以用它轻松地将隐藏在您系统中的autostart程序,spyware,adware,主页劫持,未经认证的驱动揪出来,并可以导入和导出报告以帮助别人或获取帮助.目前它可以…

华为OD机试题,用 Java 解【素数之积】问题

华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典使用说明 参加华为od机试,一定要注意不…