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…

關於源碼部分我就不詳細說明啦,也沒幾行代碼,看看就能明白。

相關文章
相關標籤/搜索