HooX: 基于Hook的React状态管理工具

CodeMaster7
• 阅读 3741

为什么又要造轮子

hook自带轮子光环

关于react hook我就不多介绍了。hook提供了抽象状态的能力,自然而然让人想到可以基于hook抽离全局状态。其天生自带轮子光环,所以社区也出现了不少基于hook的状态管理工具,比如说前阵子飞冰团队出的icestore,亦或者这个stamen,不过相对来说我更喜欢的还是这个unstated-next

那既然别人都已经造了那么多轮子了,为什么自己还要造呢?自然是因为:

别人的轮子不够用

比如说unstated-next,它本质上是把一个自定义hook全局化了。理念很好,可惜颗粒度太大了一点。必须把state、actions、effects维持在一个自定义hook中。内部的一系列actions、effects需要加useCallback、useMemo也比较麻烦,如果抽离到外部,又要传很多参数,写TS的话,还要写不少泛型。总之,如果项目相对比较复杂,写起来比较累。

stamen 其实也不错。声明一个store,包含state、reducer、effects。而且不需要给组件包裹Provider,各个地方随意拔插,响应更新。就是dispatch我不太喜欢用,不太好直接定位到action或effect的声明,且丢了入参出参类型。

icestore 的问题也差不多。说是支持TS,其实是残缺的,看了下源码,类型完全都丢失了。另外命名空间这一套我也不是很喜欢。

当然上述这些问题人家也能优化。但是何必呢,本来也没几行代码,给人家提PR的时间,我自己都写好轮子了。所以总而言之,还是自己造吧。

我的理想型

那我自己想要的状态管理工具是怎么样的呢?在hoox之前呢,其实我还实现了一版,基本复制dva的api的一个版本(把 yield 换成 async/await )。有点儿像icestore,只不过没有命名空间。最致命且无法解决的问题就是丢失了类型,丢失了函数引用。

后来我总结了一下,我真正想要的是怎么样的:

  1. 全局状态管理,且非单一store;
  2. actions跟effects就是正常的函数,独立声明,直接引用;
  3. 完美的TS支持。

所以目标很简单,可以说就是 unstated-next 的去hook包裹版。于是我实现了一版,最终效果如下:

HooX

创建全局状态

// store.js
import createHoox from 'hooxjs'

// 声明全局初始状态
const state = {
  count: 1
}

// 创建store
export const { setHoox, getHoox, useHoox } = createHoox(state)

// 创建一个 action
export const up = () => setHoox(({ count }) => ({ count: count + 1 }))

// 创建一个effect 
export const effectUp = () => {
  const [{ count }, setHoox] = getHoox();
  const newState = { count: count + 1 }
  return fetch('/api/up', newState).then(() => setHoox(newState))
  // 或者直接引用action
  // return fetch('/api/up', newState).then(up)
}

消费状态

import { useHoox, up, effectUp } from './store';

function Counter() {
  const [state] = useHoox()
  return (
    <div>
      <div>{state.count}</div>
      <button onClick={up}>up</button>
      <button onClick={effectUp}>effectUp</button>
    </div>
  )
}

直接修改状态

import { useHoox } from './store';

function Counter() {
  const [state, setHoox] = useHoox()
  return (
    <div>
      <div>{state.count}</div>
      <input
        value={state.count}
        onChange={value => setHoox({ count: value })}
      /
    </div>
  )
}

重置状态

我们知道,在class组件中,通过 this.setState 是做状态的合并更新。但是在function组件中, useState 返回的第二个参数 setState 又是做替换更新。实际使用中,其实我们都有诉求。尤其是非TS的项目,状态模型可能是动态的,很可能需要做重置状态。为了满足所有人的需求,我也加了个api方便大家使用

import { useHoox } from './store';

function Counter() {
  const [state, setHoox, resetHoox] = useHoox()
  return (
    <div>
      {state ? <div>{state.count}</div> : null}
      <button onClick={() => resetHoox(null)>reset</button>
    </div>
  )
}

全局computed

通过上述api,其实我们还可以实现类似vue中 computed 的效果。

import { useHoox } from './store';

export function useDoubleCount () {
  const [{ count }] = useHoox();
  return count * 2
}

对于某些非常复杂的运算,我们也可以使用 react 的 useMemo 做优化。

import { useHoox } from './store';

export function usePowCount (number = 2) {
  const [{ count }] = useHoox();
  return useMemo(() => Math.pow(count, number), [count, number])
}

除此外,也可以实现一些全局effect。

不够美好的地方

需要Provider

hoox底层基于 context 跟 useState 实现,由于把状态存在 context 了中,故而类似Redux,消费状态的组件必须是相应 Context.Provider 的子孙组件。如:

import { Provider } from './store';
import Counter from './counter';

function App() {
  return <Provider>
    <Counter />
  </Provider>
}

这进而导致了,如果一个组件需要消费两个store,那就需要成为两个 Provider 的子孙组件。

hoox提供了一个语法糖 createContainer ,可以稍微的简化一下语法。

import { createContainer } from './store';
import Counter from './counter';

function App () {
  return <Counter />
}
  
export default createContainer(App)

其他不好的地方

留给评论区

Github

具体的源码跟api介绍可以见github:https://github.com/wuomzfx/hoox

关于源码部分我就不详细说明啦,也没几行代码,看看就能明白。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
海军 海军
4年前
React Hook丨用好这9个钩子,所向披靡
ReactHook指南什么是Hook?Hook是React16.8的新增特性。它可以让你在不编写class的情况下使用state以及其他的React特性。Hook本质上就是一个函数,它简洁了组件,有自己的状态管理,生命周期管理,状态共享。useStateuseEffectuseContextus
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
6个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Stella981 Stella981
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Easter79 Easter79
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
3年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
CodeMaster7
CodeMaster7
Lv1
用时间和心看人,而不是用眼睛。
文章
4
粉丝
0
获赞
0