首页 > 作文

基于React Hooks的小型状态管理详解

更新时间:2023-04-04 07:44:44 阅读: 评论:0

目录
实现基徐志摩 偶然于 react hooks 的状态共享使用感受

本文主要介绍一种基于 react hooks 的状态共享方案,介绍其实现,并总结一下使用感受,目的是在状态管理方面提供多一种选择方式。

实现基于 react hooks 的状态共享

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

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

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

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

import {  dispatch,  tstateaction,  ucallback,  ueffect,  ureducer,  uref,  ustate,} from 'react';/** * @e https://github.com/facebook/react/blob/bb88ce95a87934a655ef842af776c164391131ac/packages/shared/objectis.js * inlined object.is polyfill to avoid requiring consumers ship their own * https://developer.mozilla.org/en-us/docs/web/javascript/reference/global_objects/object/is */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;/** * @e https://github.com/facebook/react/blob/933880b4544a83ce54c8a47f348effe725a58843/packages/shared/shallowequal.js * performs equality by iterating through keys on an object and returning fal * when any key has values which are not strictly equal between the arguments. * returns true when the values of all keys are strictly equal. */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 fal;  }  const keysa = object.keys(obja);  const keysb = object.keys(objb);  if (keysa.length !== keysb.length) {    return fal;  }  // 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 fal;    }  }  return true;}const uforceupdate = () => ureducer(() => ({}), {})[1] as voidfunction;type isubscriber<t> = (prevstate: t, nextstate: t) => void;export interface isharedstate<t> {  /** 静态方式获取数据, 适合在非组件中或者数据无绑定视图的情况下使用 */  get: () => t;  /** 修改数据,赋予新值 */  t: dispatch<tstateaction<t>>;  /** (浅)合并更新数据 */  update: dispatch<partial<t>>;英语经典语句  /** hooks方式获取数据, 适合在组件中使用, 数据变更时会自动重渲染该组件 */  u: () => t;  /** 订阅数据的变更 */  subscribe: (cb: isubscriber<t>) => () => void;  /** 取消订阅数据的变更 */  unsubscribe: (cb: isubscriber<t>) => void;  /** 筛出部分 state */  upick<r>(picker: (state: t) => r, deps?: readonly any[]): r;}export type ireadonlystate<t> = omit<isharedstate<t>, 't' | 'update'>;/** * 创建不同实例之间可以共享的状态 * @param initialstate 初始数据 */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 t = (next: tstateaction<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 u = () => {    const forceupdate = uforceupdate();    ueffect(() => {      let ismounted = true;      // 组件挂载后立即更新一次, 避免无法使用到第一次更新数据      forceupdate();      const un = subscribe(() => {        if (!ismounted) return;        forceupdate();      });      return () => {        un();        ismounted = fal;      };    }, []);    return state;  };  const upick = <r>(picker: (s: t) => r, deps = []) => {    const ref = uref<any>({});    ref.current.picker = picker;    const [pickedstate, tpickedstate] = ustate<r>(() =>      ref.current.picker(state),    );    ref.current.oldstate = pickedstate;    const sub = ucallback(() => {      const pickedold = ref.此曲只应天上有打一成语current.oldstate;      const pickednew = ref.current.picker(state);      if (!shallowequal(pickedold, pickednew)) {        // 避免 pickednew 是一个 function        tpickedstate(() => pickednew);      }    }, []);    ueffect(() => {      const un = subscribe(sub);      return un;    }, []);    ueffect(() => {      sub();    }, [...deps]);    return pickedstate;  };  return {    get,    t,    update: (input: partial<t>) => {      t((pre) => ({        ...pre,        ...input,      }));    },    u,    subscribe,    unsubscribe,    upick,  };};

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

// 创建一个状态实例const countstate = createsharedstate(0);const a = () => {  // 在组件中使用 hooks 方式获取响应式数据  const count = countstate.u();  return <div>a: {count}</div>;};const b = () => {  // 使用 t 方法修改数据  return <button onclick={() => countstate.t(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.upick((state) => state.a);  return <div>a: {a}</div>;};

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

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

/** * 状态派生(或 computed) * ```ts * const count1 = createsharedstate(1); * const count2 = createsharedstate(2); * const count3 = createderivedstate([count1, count2], ([n1, n2]) => n1 + n2); * ``` * @param stores * @param fn * @param initialvalue * @returns */export function createderivedstate<t = any>(  stores: ireadonlystate<any>[],  fn: (values: any[]) => t,  opts?: {    /**     * 是否同步响应     * @default fal     */    sync?: boolean;  },): ireadonlystate<t> & {  stop: () => void;} {  const { sync } = { sync: fal, ...opts };  let values: any[] = stores.map((it) => it.get());  const innermodel = createsharedstate<t>(fn(values));  let promi: promi<void> | null = null;  const uns = stores.map((it, i) => {    return it.subscribe((_old, newvalue) => {      values[i] = newvalue;      if (sync) {        innermodel.t(() => fn(values));        return;      }      // 异步更新      promi =        promi ||        promi.resolve().then(() => {          innermodel.t(() => fn(values));          promi = null;        });    });  });  return {    get: innermodel.get,    u: innermodel.u,    subscribe: innermodel.subscribe,    unsubscribe: innermodel.unsubscribe,    upick: innermodel.upick,    stop: () => {      uns.foreach((un) => un());    },  };}

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

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

使用感受

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

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

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

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

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

const countstate = createsharedstate(0);const app = () => {  ueffect(() => {    tinterval(() => {      console.log(countstate.get());    }, 1000);  }, []);  // return ...};

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

const countstate = createsharedstate(0);const content = () => {  const count = countstate.u();  return <div>{count}</div>;};const a = () => (  <button    onclick={() => {      dialog.info({        title: 'alert',        content: <content />,      });    }}  >    open  </button>);

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

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

以上,便是本文的全部内容,实际上 hooks 到目前流行了这么久,社区当中已有不少新型的状态共享实现方式,这里仅作为一种参考。

根据以上特点,这种方式有明显的优点,也有致命的缺陷(对于 ssr 而言),但在实际使用中,可以根据具体的情况来选择合适的方式。比如在 taro2 的小程序应用中,无需关心 ssr,那么我更倾向于这种方式;如果在 ssr 的同构项目中,那么定还是老老实实选择 redux。

总之,是多了一种选择,到底怎么用还得视具体情况而定。

以上就是基于react hooks的小型状态管理详解的详细内容,更多关于react hooks 小型状态管理的资料请关注www.887551.com其它相关文章!

本文发布于:2023-04-04 07:43:00,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/zuowen/d71e8d8bf685582da8013522684fc532.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

本文word下载地址:基于React Hooks的小型状态管理详解.doc

本文 PDF 下载地址:基于React Hooks的小型状态管理详解.pdf

标签:状态   数据   方式   组件
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图