其實在一個多月前,我也已經在掘金髮過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
關於源碼部分我就不詳細說明啦,也沒幾行代碼,看看就能明白。