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

为何又要造轮子

hook自带轮子光环

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

那既然别人都已经造了那么多轮子了,为何本身还要造呢?天然是由于:vue

别人的轮子不够用

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

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

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

固然上述这些问题人家也能优化。可是何须呢,原本也没几行代码,给人家提PR的时间,我本身都写好轮子了。因此总而言之,仍是本身造吧。github

个人理想型

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

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

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

因此目标很简单,能够说就是 unstated-next 的去hook包裹版。因而我实现了一版,最终效果以下:ide

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:github.com/wuomzfx/hoo…

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

相关文章
相关标签/搜索