我的编程空间,编程开发者的网络收藏夹
学习永远不晚

如何实现基于React Hooks的状态共享

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

如何实现基于React Hooks的状态共享

这篇文章主要介绍了如何实现基于React Hooks的状态共享,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。

实现基于 React Hooks 的状态共享

React 组件间的状态共享,是一个老生常谈的问题,也有很多解决方案,例如 Redux、MobX 等。这些方案很专业,也经历了时间的考验,但私以为他们不太适合一些不算复杂的项目,反而会引入一些额外的复杂度。

实际上很多时候,我不想定义 mutation 和 action、我不想套一层 context,更不想写 connect 和 mapStateToProps;我想要的是一种轻量、简单的状态共享方案,简简单单引用、简简单单使用。

随着 Hooks 的诞生、流行,我的想法得以如愿。

接着介绍一下我目前在用的方案,将 Hooks 与发布/订阅模式结合,就能实现一种简单、实用的状态共享方案。因为代码不多,下面将给出完整的实现。

import {  Dispatch,  SetStateAction,  useCallback,  useEffect,  useReducer,  useRef,  useState,} from 'react';function is(x: any, y: any): boolean {  return (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y);}const objectIs = typeof Object.is === 'function' ? Object.is : is;function shallowEqual(objA: any, objB: any): boolean {  if (is(objA, objB)) {    return true;  }  if (    typeof objA !== 'object' ||    objA === null ||    typeof objB !== 'object' ||    objB === null  ) {    return false;  }  const keysA = Object.keys(objA);  const keysB = Object.keys(objB);  if (keysA.length !== keysB.length) {    return false;  }  // Test for A's keys different from B.  for (let i = 0; i < keysA.length; i++) {    if (      !Object.prototype.hasOwnProperty.call(objB, keysA[i]) ||      !is(objA[keysA[i]], objB[keysA[i]])    ) {      return false;    }  }  return true;}const useForceUpdate = () => useReducer(() => ({}), {})[1] as VoidFunction;type ISubscriber<T> = (prevState: T, nextState: T) => void;export interface ISharedState<T> {    get: () => T;    set: Dispatch<SetStateAction<T>>;    update: Dispatch<Partial<T>>;    use: () => T;    subscribe: (cb: ISubscriber<T>) => () => void;    unsubscribe: (cb: ISubscriber<T>) => void;    usePick<R>(picker: (state: T) => R, deps?: readonly any[]): R;}export type IReadonlyState<T> = Omit<ISharedState<T>, 'set' | 'update'>;export const createSharedState = <T>(initialState: T): ISharedState<T> => {  let state = initialState;  const subscribers: ISubscriber<T>[] = [];  // 订阅 state 的变化  const subscribe = (subscriber: ISubscriber<T>) => {    subscribers.push(subscriber);    return () => unsubscribe(subscriber);  };  // 取消订阅 state 的变化  const unsubscribe = (subscriber: ISubscriber<T>) => {    const index = subscribers.indexOf(subscriber);    index > -1 && subscribers.splice(index, 1);  };  // 获取当前最新的 state  const get = () => state;  // 变更 state  const set = (next: SetStateAction<T>) => {    const prevState = state;    // @ts-ignore    const nextState = typeof next === 'function' ? next(prevState) : next;    if (objectIs(state, nextState)) {      return;    }    state = nextState;    subscribers.forEach((cb) => cb(prevState, state));  };  // 获取当前最新的 state 的 hooks 用法  const use = () => {    const forceUpdate = useForceUpdate();    useEffect(() => {      let isMounted = true;      // 组件挂载后立即更新一次, 避免无法使用到第一次更新数据      forceUpdate();      const un = subscribe(() => {        if (!isMounted) return;        forceUpdate();      });      return () => {        un();        isMounted = false;      };    }, []);    return state;  };  const usePick = <R>(picker: (s: T) => R, deps = []) => {    const ref = useRef<any>({});    ref.current.picker = picker;    const [pickedState, setPickedState] = useState<R>(() =>      ref.current.picker(state),    );    ref.current.oldState = pickedState;    const sub = useCallback(() => {      const pickedOld = ref.current.oldState;      const pickedNew = ref.current.picker(state);      if (!shallowEqual(pickedOld, pickedNew)) {        // 避免 pickedNew 是一个 function        setPickedState(() => pickedNew);      }    }, []);    useEffect(() => {      const un = subscribe(sub);      return un;    }, []);    useEffect(() => {      sub();    }, [...deps]);    return pickedState;  };  return {    get,    set,    update: (input: Partial<T>) => {      set((pre) => ({        ...pre,        ...input,      }));    },    use,    subscribe,    unsubscribe,    usePick,  };};

拥有 createSharedState 之后,下一步就能轻易地创建出一个可共享的状态了,在组件中使用的方式也很直接。

// 创建一个状态实例const countState = createSharedState(0);const A = () => {  // 在组件中使用 hooks 方式获取响应式数据  const count = countState.use();  return <div>A: {count}</div>;};const B = () => {  // 使用 set 方法修改数据  return <button onClick={() => countState.set(count + 1)}>Add</button>;};const C = () => {  return (    <button      onClick={() => {        // 使用 get 方法获取数据        console.log(countState.get());      }}    >      Get    </button>  );};const App = () => {  return (    <>      <A />      <B />      <C />    </>  );};

对于复杂对象,还提供了一种方式,用于在组件中监听指定部分的数据变化,避免其他字段变更造成多余的 render:

const complexState = createSharedState({  a: 0,  b: {    c: 0,  },});const A = () => {  const a = complexState.usePick((state) => state.a);  return <div>A: {a}</div>;};

但复杂对象一般更建议使用组合派生的方式,由多个简单的状态派生出一个复杂的对象。另外在有些时候,我们会需要一种基于原数据的计算结果,所以这里同时提供了一种派生数据的方式。

通过显示声明依赖的方式监听数据源,再传入计算函数,那么就能得到一个响应式的派生结果了。

export function createDerivedState<T = any>(  stores: IReadonlyState<any>[],  fn: (values: any[]) => T,  opts?: {        sync?: boolean;  },): IReadonlyState<T> & {  stop: () => void;} {  const { sync } = { sync: false, ...opts };  let values: any[] = stores.map((it) => it.get());  const innerModel = createSharedState<T>(fn(values));  let promise: Promise<void> | null = null;  const uns = stores.map((it, i) => {    return it.subscribe((_old, newValue) => {      values[i] = newValue;      if (sync) {        innerModel.set(() => fn(values));        return;      }      // 异步更新      promise =        promise ||        Promise.resolve().then(() => {          innerModel.set(() => fn(values));          promise = null;        });    });  });  return {    get: innerModel.get,    use: innerModel.use,    subscribe: innerModel.subscribe,    unsubscribe: innerModel.unsubscribe,    usePick: innerModel.usePick,    stop: () => {      uns.forEach((un) => un());    },  };}

至此,基于 Hooks 的状态共享方的实现介绍就结束了。

在最近的项目中,有需要状态共享的场景,我都选择了上述方式,在 Web 项目和小程序 Taro 项目中均能使用同一套实现,一直都比较顺利。

使用感受

最后总结一下目前这种方式的几个特点:

实现简单,不引入其他概念,仅在 Hooks 的基础上结合发布/订阅模式,类 React 的场景都能使用,比如 Taro;

使用简单,因为没有其他概念,直接调用 create 方法即可得到 state 的引用,调用 state 实例上的 use 方法即完成了组件和数据的绑定;

类型友好,创建 state 时无需定义多余的类型,使用的时候也能较好地自动推导出类型;

避免了 Hooks 的“闭包陷阱”,因为 state 的引用是恒定的,通过 state 的 get 方法总是能获取到最新的值:

const countState = createSharedState(0);const App = () => {  useEffect(() => {    setInterval(() => {      console.log(countState.get());    }, 1000);  }, []);  // return ...};

直接支持在多个 React 应用之间共享,在使用一些弹框的时候是比较容易出现多个 React 应用的场景:

const countState = createSharedState(0);const Content = () => {  const count = countState.use();  return <div>{count}</div>;};const A = () => (  <button    onClick={() => {      Dialog.info({        title: 'Alert',        content: <Content />,      });    }}  >    open  </button>);

支持在组件外的场景获取/更新数据

在 SSR 的场景有较大局限性:state 是细碎、分散创建的,而且 state 的生命周期不是跟随 React 应用,导致无法用同构的方式编写 SSR 应用代码

感谢你能够认真阅读完这篇文章,希望小编分享的“如何实现基于React Hooks的状态共享”这篇文章对大家有帮助,同时也希望大家多多支持编程网,关注编程网行业资讯频道,更多相关知识等着你来学习!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

如何实现基于React Hooks的状态共享

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

如何实现基于React Hooks的状态共享

这篇文章主要介绍了如何实现基于React Hooks的状态共享,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。实现基于 React Hooks 的状态共享React 组件间的状
2023-06-22

react如何实现组件状态缓存

这篇文章主要介绍“react如何实现组件状态缓存”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“react如何实现组件状态缓存”文章能帮助大家解决问题。一、安装第三方库npm i react-acti
2023-07-05

react如何实现跳转前记住页面状态

这篇文章主要介绍了react如何实现跳转前记住页面状态的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇react如何实现跳转前记住页面状态文章都会有所收获,下面我们一起来看看吧。react实现跳转前记住页面状态的
2023-07-04

基于Echarts如何实现绘制立体柱状图

本篇内容主要讲解“基于Echarts如何实现绘制立体柱状图”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“基于Echarts如何实现绘制立体柱状图”吧!实现方法先写一个常规的柱状图在这个基础上进行
2023-07-05

基于JavaScript如何实现动态雨滴特效

今天小编给大家分享一下基于JavaScript如何实现动态雨滴特效的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。演示技术栈介
2023-07-02

windows共享的打印机状态正常但打印不出来如何解决

这篇文章主要介绍了windows共享的打印机状态正常但打印不出来如何解决的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇windows共享的打印机状态正常但打印不出来如何解决文章都会有所收获,下面我们一起来看看吧
2023-03-06

基于SpringBoot和Vue的动态语音播放如何实现

这篇文章主要介绍“基于SpringBoot和Vue的动态语音播放如何实现”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“基于SpringBoot和Vue的动态语音播放如何实现”文章能帮助大家解决问题。
2023-07-06

如何实现网络连接状态建立于断开的VBS脚本

这篇文章给大家分享的是有关如何实现网络连接状态建立于断开的VBS脚本的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。断开连接提示 代码如下:strComputer = "." Set objWMIService =
2023-06-08

基于webman的GraphQL如何实现

这篇“基于webman的GraphQL如何实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“基于webman的GraphQL
2023-07-05

windows和Linux之间的文件共享如何实现

这篇文章主要介绍了windows和Linux之间的文件共享如何实现的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇windows和Linux之间的文件共享如何实现文章都会有所收获,下面我们一起来看看吧。一、接禁G
2023-07-06

如何实现删除默认共享的批处理

这篇文章将为大家详细讲解有关如何实现删除默认共享的批处理,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。代码如下:@echo off ::::::::::::::::::::::::::::::::::::
2023-06-08

flink中如何实现有状态stateful的计算

小编给大家分享一下flink中如何实现有状态stateful的计算,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!import org.apache.flink.a
2023-06-02

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录