返回顶部
首页 > 资讯 > 精选 >如何实现基于React Hooks的状态共享
  • 579
分享到

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

2023-06-22 06:06:09 579人浏览 八月长安
摘要

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

这篇文章主要介绍了如何实现基于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的状态共享”这篇文章对大家有帮助,同时也希望大家多多支持编程网,关注编程网精选频道,更多相关知识等着你来学习!

--结束END--

本文标题: 如何实现基于React Hooks的状态共享

本文链接: https://lsjlt.com/news/302963.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 如何实现基于React Hooks的状态共享
    这篇文章主要介绍了如何实现基于React Hooks的状态共享,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。实现基于 React Hooks 的状态共享React 组件间的状...
    99+
    2023-06-22
  • 基于React Hooks的小型状态管理详解
    目录实现基于 React Hooks 的状态共享使用感受本文主要介绍一种基于 React Hooks 的状态共享方案,介绍其实现,并总结一下使用感受,目的是在状态管理方面提供多一种选...
    99+
    2024-04-02
  • React如何使用Hooks简化受控组件的状态绑定
    这篇文章主要介绍React如何使用Hooks简化受控组件的状态绑定,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!ECMAScript 6文章中大量用到了 ES6 语法,比如解构赋值和...
    99+
    2024-04-02
  • react如何实现组件状态缓存
    这篇文章主要介绍“react如何实现组件状态缓存”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“react如何实现组件状态缓存”文章能帮助大家解决问题。一、安装第三方库npm i ...
    99+
    2023-07-05
  • React 数据共享useContext的实现
    因为疫情, 最近接手一个新项目。是React的。上次写React已经过去1年多了。 虽然捡起来也不是什么难事,不过技术这个东西,长时间不用就容易忘记。 为了保证这个项目根其他平行项目...
    99+
    2024-04-02
  • 基于JavaScript实现文件共享型网站
    目录特色说明如何使用代码审查总结参考Any Share 是一种简单、轻量、快速的文件共享服务。使用 Javascript 编写,并搭建在 Firebase 平台。 特色 上传文件 下...
    99+
    2022-11-13
    JavaScript文件共享网站 JavaScript文件共享 JavaScript 网站
  • ubuntu如何实现共享
    ubuntu实现共享的示例:在Window下,创建一个共享文件夹,并设置“共享”属性,右键共享文件夹,选择“共享”,在弹出的菜单中,选择“共享的用户”,设置共享用户。在ubuntu计算机上打开“主文件夹”,然后选择网络下的“连接到服务器”。...
    99+
    2024-04-02
  • react如何实现跳转前记住页面状态
    这篇文章主要介绍了react如何实现跳转前记住页面状态的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇react如何实现跳转前记住页面状态文章都会有所收获,下面我们一起来看看吧。react实现跳转前记住页面状态的...
    99+
    2023-07-04
  • 基于PHP的图片共享系统的设计与实现
    基于PHP的图片共享系统的设计与实现 摘 要 本系统主要从现代社会电脑化观念出发,通过对现有资料的分析、研究和整理,确定了在基于现存的WEB2.0模式下开发图片共享系统的可行性、紧迫性和必要性。在现阶...
    99+
    2023-09-01
    php 开发语言
  • react18中react-redux状态管理的实现
    react的状态管理还是挺多的现在流行的有以下五种: RecoilMobXXStateReduxContext 今天我们来讲一下react众多状态管理之一的redux,虽然这个我不太...
    99+
    2024-04-02
  • Redis如何实现Session共享
    这篇文章运用简单易懂的例子给大家介绍Redis如何实现Session共享,代码非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。Redis实现Session共享这几天在做session共享这么一...
    99+
    2024-04-02
  • Go如何实现共享库
    本篇内容主要讲解“Go如何实现共享库”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Go如何实现共享库”吧!共享库使用共享库可以协助我们管理代码重用的问题,但是需要考虑共享库依赖和变更控制的问题。...
    99+
    2023-07-05
  • 如何实现基于react+webpack的多页面应用
    这篇文章给大家分享的是有关如何实现基于react+webpack的多页面应用的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。项目目录介绍:打包后文件目录:打包成cli如果你厌烦了新...
    99+
    2024-04-02
  • ubuntu18.04如何实现桌面共享
    ubuntu18.04实现桌面共享的方法:打开终端,输入命令安装xrdp,命令如:sudo apt install xrdp输入以下命令打开startwm.sh配置文件,命令如:sudo vim /etc/xrdp/startwm.sh将配...
    99+
    2024-04-02
  • ubuntu如何实现文件共享
    本文小编为大家详细介绍“ubuntu如何实现文件共享”,内容详细,步骤清晰,细节处理妥当,希望这篇“ubuntu如何实现文件共享”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。ubuntu可以使用Smb协议实现文件...
    99+
    2023-07-04
  • Spring session如何实现Session共享
    这篇文章主要介绍“Spring session如何实现Session共享”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Spring session如何实现Session共享”文章...
    99+
    2023-07-06
  • React使用有限状态机的实现示例
    目录什么是有限状态机?有限状态机的示例有限状态机和软件开发、计算机科学有什么关系?举了那么多例子,但我该怎么展示在前端开发中使用状态机呢?React 应用程序中的注册表单的传统实现方...
    99+
    2024-04-02
  • Mobx实现React 应用的状态管理详解
    目录MobX从一个 demo 开始创建类并将其转化成可观察对象使用可观察对象MobX 与 React 集成在组件中使用可观察对象1. 访问全局的类实例2. 通过 props3. 通过...
    99+
    2022-12-08
    Mobx React 应用状态管理 Mobx React
  • 基于Echarts如何实现绘制立体柱状图
    本篇内容主要讲解“基于Echarts如何实现绘制立体柱状图”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“基于Echarts如何实现绘制立体柱状图”吧!实现方法先写一个常规的柱状图在这个基础上进行...
    99+
    2023-07-05
  • 基于PyQt5实现状态栏(statusBar)显示和隐藏功能
    首先,讲述要解决的两个问题以及解决问题的代码: 1、将鼠标放置于菜单栏上状态栏永久消失的问题(这个问题需要仔细观察才能注意到) # 此覆盖父类函数: 覆盖方法; 为了克服 将...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作