13行代碼實現狀態管理工具

本文須要先行了解 Hooks 的基礎知識。html

React 狀態管理實現有兩種,一種是 Flux 架構的,例如 Redux,其經過 Context 實現全局狀態共享;另外一種是響應式的,例如 Mobx,經過可觀測對象和 HOC 實現狀態共享。react

在 Hooks 出來後,以前的經過 props 的狀態解決方案就有些過於繁瑣了,鑑於以前的狀態管理的複雜,前兩天我寫了一個狀態管理工具 Piex Store,徹底面向對象,基於 Hooks,不借助於 Context 實現狀態共享。git

本文將基於其核心原理,逐步實現一個最簡單的狀態管理工具,其核心代碼只有 13 行。github

Custom Hook

Hooks API 出來後,Function Component(函數組件,如下簡稱 FC) 也有了本身的狀態,並且能夠自定義 Custom Hook,這便讓咱們對組件狀態有個更多的操做可能性。如下是一個簡單的自定義 Hooks:redux

const useCounter = () => {
    const [count, setCount] = useState(0);
    
    const increment = useCallback(() => {
        setCount(count + 1); 
    }, [count]);
    
    const decrement = useCallback(() => {
        setCount(count - 1); 
    }, [count]);
    
    return {count, increment, decrement};
}
複製代碼

若是要使用的話須要在一個 FC 裏:數組

const Counter = () => {
  const {count, increment, decrement} = useCounter();

  return (
    <article> <p>{count}</p> <button onClick={increment}>Increment</button> <button onClick={decrement}>Decrement</button> </article>
  )
}
複製代碼

這樣咱們就實現了一個簡單的 custom hooks,能夠複用對 count 的操做邏輯。markdown

可是這裏有一個問題,你們能夠運行這個 例子,咱們使用兩個 Counter 組件的話,它們的 count 是互不關聯的,二者之間沒有任何關係。若是咱們有辦法讓 count 共享,每次修改狀態就能在全部用到的地方同步到狀態,不就是狀態共享嗎!架構

控制組件更新

共享狀態的一個問題就是如何把狀態同步到全部組件並更新頁面,其實經過 useState 就能夠輕鬆實現,下面咱們看一個例子:frontend

const App = () => {
  const [,setState] = useState(Math.random());

  setTimeout(()=>{
    setState(Math.random());
  }, 200);

  return (
    <p>{Date.now()}</p>
  )
};
複製代碼

點擊 這裏 查看實際運行效果。dom

能夠發現這裏沒有取 useState 的第一個參數,而只是用了第二個參數更新狀態,實際上每 200ms 後頁面上就顯示不一樣的時間戳。

其實 useState 並非什麼黑魔法,具體實現原理能夠看個人 這篇文章。簡單來說,就是咱們每次 setState 時,若是參數和前一個 state 不相等,React 就會從新運行函數組件,把返回的值作 DOM Diff 來更新頁面,因此關鍵就在於 setState,若是咱們掌握了 setState,就掌握了更新組件的時機。

狀態共享

咱們經過上面的例子知道 useState 返回數組的第二個值,這裏稱爲 setState,能夠控制組件的渲染。

那麼若是有一個方法,把全部用到共享狀態的組件都建立一個 useState,並把第二個參數存儲起來,每次更新共享狀態時,把全部的 setState 都運行一遍,那麼不就能夠更新全部組件的狀態了嗎?不就實現狀態共享了嗎?

下面咱們經過一個簡單的例子看一下:

let _count = 0;
let _setters = [];

const useCounter = () => {
    const [, setCount] = useState(_count);

    _setters.push(setCount);
    
    const increment = useCallback(() => {
      _count++;
      _setters.forEach(setState => setState(_count));
    }, [_count]);
    
    const decrement = useCallback(() => {
      _count--;
      _setters.forEach(setState => setState(_count));
    }, [_count]);
    
    return {count:_count, increment, decrement};
}

const Counter = () => {
  const {count, increment, decrement} = useCounter();

  return (
    <article> <p>{count}</p> <button onClick={increment}>Increment</button> <button onClick={decrement}>Decrement</button> </article>
  )
}

const App = () => {
  return (
    <div> <Counter /> <Counter /> </div>
  )
}
複製代碼

運行效果點擊 這裏 查看。

這個例子是基於第一個 custom hook 的例子改動的,能夠發現點擊一個按鈕,頁面上兩個 Counter 組件的 count 值都變了。

這是由於我把 useCounter 的狀態存到全局的 _count 變量中了,而且把 useState 的第二個參數也都收集到全局的 _setters 數組中了,每次操做 increment 或者 decrement 時,就會先改變 _count 的值,而後觸發 setters 中的 setState,這樣全部 Counter 組件都會更新啦。並且返回的是 _count 變量重命名爲 count,因此每一個組件的 setState 被觸發後都會經過 _count 獲得最新的值並顯示在頁面上。

這樣咱們就實現了一個最簡單的計數器狀態共享,可是每次都本身寫太麻煩了,能夠設計一個簡單易用,立馬能夠上手的通用工具使用。

通用工具

設計一個通用工具須要看應用場景和通用模型,面向對象是一個不錯的選擇。關於更多細節能夠看 Piex Store 核心概念 來了解,咱們看一下怎麼實現:

export abstract class Store {
  state = {};
  setters = [];

  setState(newState) {
    this.state = newState;
    this.setters.forEach(setState => setState(this.state));
  }
}

export function useStore(store) {
  let [, setState] = useState(store.state);
  store.setters.push(setState);

  return store;
}
複製代碼

因爲 JS 不支持繼承,因此這段代碼用 TS 實現,去掉空行,短短 13 行代碼就實現了一個狀態管理:

  • state 對應上例中的 _count,存儲全局狀態;
  • setters 對應上例中的 _setters,收集 setState;
  • setState 方法用來更新組件;
  • useStore 則用來收集依賴;

具體怎麼使用呢?以下:

class CounterStore extends Store {
  state = {
    count: 0,
  }

  increment() {
    this.setState({
      count: this.state.count + 1,
    })
  }

  decrement() {
    this.setState({
      count: this.state.count + 1,
    })
  }
}

const counterStore = new CounterStore();

const Counter = () => {
  const store = useStore(counterStore);

  return (
    <article> <p>{store.state.count}</p> <button onClick={store.increment}>Increment</button> <button onClick={store.decrement}>Decrement</button> </article>
  )
}

const App = () => {
  return (
    <div> <Counter /> <Counter /> </div>
  )
}
複製代碼

這樣,全部用到 counterStore 的地方均可以共享 counterStore.state 的變量,還能夠經過對象方法來更新 state 並同步到其它組件。

最後

固然,這只是一個簡單的 demo,不少東西都沒有作,如 setters 收集的 setState 依賴在組件卸載時釋放;全量更新狀態太麻煩,僅部分更新就能夠了,還有數據變動檢測等等。

這些確定不是 13 行代碼能夠實現的了,若是感興趣能夠看 Piex Store,基於上述原理實現的狀態管理工具,完美支持 TS 類型推斷,聽從 React 設計哲學,支持中間件,可使用 Redux DevTools 觀察狀態變動。

因爲 Piex Store 還處於襁褓狀態,不少地方還有須要完善的地方,還請你們多多支持。

相關文章
相關標籤/搜索