其实在一个多月前,我也已经在掘金发过hoox 的介绍。不过我以为这么简单的东西,也没太大技术含量,也就是随便发发看,图个乐。最近发现基于 Hook 的状态管理器愈来愈多了,那我也就再在这里赶个集好了,省得之后再发显得有点儿山寨。javascript
另外,我仍是提早说,目前个人这个小玩具,仍是 0.x 的版本。我还不敢发正式版,一方面是我本身以为还有些未完善之处。另外一方面,是它确实没有通过很是多项目的考验。不过,若是纯按业务流量来讲,它已经在蚂蚁保险几个百万~千万 UV 级的 C 端页面上跑了好久了。目前来看,没有明显异常。这也是我发文章时稍微有的一丁点儿底气。vue
回归正文:java
关于 react hook 我就很少介绍了。hook 提供了抽象状态的能力,天然而然让人想到能够基于 hook 抽离全局状态。其天生自带轮子光环,因此社区也出现了很多基于 hook 的状态管理工具,好比说前阵子飞冰团队出的icestore,亦或者这个stamen,不过相对来讲我更喜欢的仍是这个unstated-next。react
那既然别人都已经造了那么多轮子了,为何本身还要造呢?天然是由于:git
好比说unstated-next,它本质上是把一个自定义 hook 全局化了。理念很好,就是状态逻辑比较复杂的话,写起来有点儿累。必须把 state、actions、effects 维持在一个自定义 hook 中。内部的一系列 actions、effects 须要加 useCallback、useMemo 也比较麻烦,若是抽离到外部,又要传不少参数。总之,若是项目相对比较复杂,写起来比较累。github
stamen 其实也不错。声明一个 store,包含 state、reducer、effects。并且不须要给组件包裹 Provider,各个地方随意拔插,响应更新。就是 dispatch 我不太喜欢用,不太好直接定位到 action 或 effect 的声明,且丢了入参出参类型。redux
icestore 的问题也差很少。说是支持 TS,实际上是残缺的,看了下源码,类型彻底都丢失了。另外命名空间这一套我也不是很喜欢。api
另外,前两天蚂蚁体验技术部的同窗也出了一个hox。名字跟个人这个很像,但确实不是一个东西。它呢有点儿像 unstated-next 跟 statemen 的结合体。按我理解,它核心就是想在 unstated-next 的基础上,解决嵌套Provider
的问题。不过这也不是我使用 unstated-next 时的痛点。另外,其内部使用ReactDOM.render
来实现,无法实现 SSR。数组
固然上述这些问题人家也能优化。可是何须呢,原本也没几行代码,给人家提 PR 的时间,我本身都写好轮子了。因此总而言之,仍是本身造吧。async
那我本身想要的状态管理工具是怎么样的呢?在 hoox 以前呢,其实我还实现了一版,基本复制 dva 的 api 的一个版本(把 yield 换成 async/await )。有点儿像 icestore,只不过没有命名空间。但它有着 icestore 跟 stamen 一样的问题,不太好直接定位到 action/effect 的声明。
后来我总结了一下,我真正想要的是怎么样的:
因此目标很简单,能够说就是 unstated-next 的去 hook 包裹版。因而我实现了一版,最终效果以下:
// store.js import createHoox from 'hooxjs' // 声明全局初始状态 const state = { count: 1 } // 建立store export const { Provider, // 使用全局状态的组件或者其根组件,须要被Provider包裹 useHoox, // 获取全局状态,以及更新全局状态的方法,相似useState getHoox // 获取全局状态,相比useHoox,其获取的状态更新时,并不会触发组件更新,经常使用于effect跟action中 } = createHoox(state) // 建立一个 action export const up = () => { const [{ count }, setHoox] = getHoox() return setHoox({ count: count + 1 }) } // 建立一个 effect export const effectUp = async () => { // getHoox 跟 useHoox const [{ count }, setHoox] = getHoox() const newState = { count: count + 1 } await fetch('/api/up', newState) return setHoox(newState) // 或者直接引用action // return up() }
固然,若是action/effect
场景简单的话,也有些简单的 api。
export const { // ...其余api setHoox } = createHoox(state) // 建立一个 action export const up = () => setHoox(({ count }) => ({ count: count + 1 }))
能够看到,经过这样的方式,建立action/effect
以及全局状态就脱离 hook 了。这样的好处有:
action/effect
不在 hook 中,避免每次 render 致使的函数从新声明(进而须要useCallback/useMemo
)。在组件里使用全局状态,为了保证响应式,须要经过useHoox
获取。若是是使用action/effect
,那就比较简单了,直接引用便可。
切忌,组件不该该经过getHoox
获取全局状态,由于它不具备响应式的逻辑。虽然也能获取到状态,可是并不会由于状态的变动而触发组件 render。
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> ) }
若是场景较为简单,且不须要抽象action
,也能够直接在组件内部更新状态。
import { useHoox } from './store' function Counter() { const [state, setHoox] = useHoox() return ( <div> <div>{state.count}</div> <input value={state.count} onChange={event => setHoox({ count: event.target.value })} /> </div> ) }
若是这个组件只更改状态,不须要消费状态,也能够直接用setHoox
。
import { setHoox } from './store' function Inputer() { return ( <div> <input onChange={event => setHoox({ count: event.target.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> ) }
经过上述 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]) }
除此外,也能够实现一些全局 hooks。
其实正常来讲,个人业务代码基本不太会写connect
的...直接useHoox
便可。但也有两种状况是例外的:
class
组件不想改形成function
组件,但又要用到全局状态。因此实现了connect
这个 api,方便解决这两个问题。
首先 store 中须要暴露出这个 api。
// store.js export const { // ...其余api connect } = createHoox(state)
而后对于函数式组件:
// Counter.js import { connect } from './store' const Counter = ({ count }) => { return <div>{count}</div> } const NewCounter = connect(state => ({ count: state.count }))(Counter) export default NewCounter
被connect
之后,返回的NewCounter
,就不须要再接受count
这个prop
,这个也已经作好了类型推导。
若是想用装饰器的话,函数组件是没有办法的,不过class
能够。
import { connect } from './store' @connect(state => ({ count: state.count })) export default class Counter extends React.PureComponent { render() { return <div>{this.props.count}</div> } }
但这个装饰器仅限于 js 环境,ts 环境下,装饰器不能改变 class 的返回类型。可是实际代码中,组件被connect
后,我会返回一个新的函数式组件,而且改变了组件Props
的类型(去除了全局状态注入的 props)。所以 ts 环境下,没法正常使用装饰器。固然 使用函数包裹依旧是能够的:
import { connect } from './store' class Counter extends React.PureComponent<{ count: number }> { render() { return <div>{this.props.count}</div> } } export default connect(state => ({ count: state.count }))(Counter)
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)
虽然这样仍是有些繁琐。尤为当有多个 store 互相调用的时候,须要特殊注意,用到状态的组件是否在相应的 Provider 包裹下。但我依旧不肯意使用相似stamen
跟hox
这样发布订阅的方法。由于 react 已经有一套本身响应式逻辑了。再在上面加个发布订阅的逻辑...我能力比较差,hold 不住... 目前我也想不到更好的办法,只能提供下语法糖,稍微简化一点点。
getHoox
、useHoox
、setHoox
什么的,确实 api 看着比较多,第一次用会有点儿懵。可能还会用错。不过新手用只要切记一点:没什么特殊要求,不要在组件里使用getHoox
。 只要牢记这点,基本只要能跑通,就没啥大问题。
用一小段时间后,明白getHoox
是非响应式地获取全局状态,后续就 OK 了。最近团队里有个同窗再研究eslint-plugin
。后续让他帮忙写个hoox
的 lint 插件就能改善一部分问题了。
目前来看,我也找不到其余更好的办法能解决这个问题,我必须有这几个api
。
留给评论区
这个工具,目前咱们团队内有 5-6 我的使用。总体而言,口碑还行,尤为是对于一些中小项目。有些组件稍微繁琐一些,就会有一堆 useMemo 来,useCallback 去的逻辑。经过hoox
,将这些逻辑抽离出 hook,代码会清爽很多。另外,这些中小项目,引入dva/redux
这些工具,确实显得偏重。经过函数式组件+hoox
,即保证了轻量级,也知足了全局状态管理的场景。
可是呢,它确实还有很多缺点。并且若是是真的想吃透 99%的场景,可能还须要补充一些配套工具。包括提到的lint
插件,甚至是相似redux-devtools
这样的工具。目前的我,还不敢发正式版,也不敢拿本身部门来背书。因此这篇文章,包括在掘金,我都没有发到部门专栏。
不过若是你想用,基本仍是能够放心的用。咱们本身已经有多条业务在使用了,不是大版本,不可能 breaking change 了。只是说,它不必定是 React 状态管理工具的终态......将来大家遇到更好的,仍是可能会选择迁移。
最后总结一下就是:问题不大,欢迎使用!
具体的源码跟详细 api 介绍能够见 github:https://github.com/wuomzfx/hoox
关于源码部分我就不详细说明啦,也没几行代码,看看就能明白。