20 行簡單實現一個 unstated-next 🎅
- 2021 年 4 月 20 日
- 筆記
- javascript, React, redux, 前端
前言 📝
👉 unstated-next 基於 React 心智模型(hook+context)而設計的狀態管理。 👈
在 react hook 出現之前,有基於單一數據源,使用純函數修改狀態的 redux & react-redux 也有基於 Object.defineProperty 和 Proxy 來進行數據攔截訪問的 mobx ,但伴隨著 react 16.8 的出現,我們可以基於自帶的 hook 去實現狀態管理也就是 unstated-next
官網 Demo 🥔
...
import { createContainer } from "unstated-next";
function useCounter(initialState = 0) {
let [count, setCount] = useState(initialState);
let decrement = () => setCount(count - 1);
let increment = () => setCount(count + 1);
return { count, decrement, increment };
}
//使用 createContainer 將 useCounter改造成提供狀態和方法的組件
let Counter = createContainer(useCounter);
function CounterDisplay() {
//從被處理過的 useCounter 中拿到狀態和方法
let counter = Counter.useContainer();
return (
<div>
<button onClick={counter.decrement}>-</button>
<span>{counter.count}</span>
<button onClick={counter.increment}>+</button>
</div>
);
}
function App() {
return (
<Counter.Provider>
<CounterDisplay />
{/* 通過initialState屬性注入初始值 */}
<Counter.Provider initialState={2}>
<CounterDisplay />
</Counter.Provider>
</Counter.Provider>
);
}
render(<App />, document.getElementById("root"));
unstated-next 做了什麼?
- 提供 createContainer 將自定義 Hooks 封裝為一個可以提供狀態和方法的 數據對象
- 利用 useContext 構造了
Provider 注入
和組件獲取獲取 Store
這兩個方法
實現一個 unstated-next 🚲
import { createContext, createElement, useContext } from "react";
export default useHook => {
const Context = createContext();
const Provider = ({ init, children }) => {
return createElement(Context.Provider, { value: useHook(init) }, children);
};
const useContainer = () => useContext(Context);
return { Provider, useContainer };
};
- 通過函數返回一個包含
Provider
和useContainer
的對象 - Provider 接受 init 初始值,去執行 數據對象 組件,通過 createElement 創造一個 Context.Provider 傳值組件,並將 數據對象 組件返回的方法和狀態保存到
value
,子節點不變,返回:
<xxx.Provider value={方法,狀態...}>{children}</xxx.Provider>
- 通過
useContainer
拿到 當前 Context.Provider 中的 value 狀態和方法 並返回
如何解決 Provider hell 🏁
在 unstated-next 中每一個被處理為 數據對象 的組件如果想要被共享,需要在最外層逐級包裹
<Container1.Provider>
<Container2.Provider>
<Container3.Provider>MyApp</Container3.Provider>
</Container2.Provider>
</Container1.Provider>
我們可以通過 類似 compose 函數進行處理,將所有 數據對象 組件通過 reduce 逐級疊加返回一個類似洋蔥的 Provider,調用的時候只需要使用Provider
包裹住業務組件
export const composeProvider = (...commonFun) => ({ children }) => {
return commonFun.reduceRight((child, { init, Provider }) => {
return <Provider init={init}>{child}</Provider>;
}, children);
};
//進行調用
const Provider = reduceProvider({ ...xxxState1, init: 100 }, xxxState2);
export default () => (
<Provider>
<ExamplePage1 />
<ExamplePage2 />
<ExamplePage3 />
</Provider>
);
大功告成!
總結 💢
總結
其實 unstated-next 實現很簡單,通俗來講就是一個閉包,使用於簡單的業務場景,且寫法過於靈活,一旦遇到 class 組件的情況,就又要回到舊的寫法,所以只能說有利有弊
至此,謝謝各位在百忙之中點開這篇文章,希望對你們能有所幫助,相信你對 unstated-next 有了大概的認實,如有問題歡迎各位大佬指正。
歡迎添加我的微信共同討論前端技術問題(備註:qian)
參考文獻
- 🍑:unstated-next
- 🍑:React hooks,組合與抽象,狀態管理
- 🍑:精讀《unstated 與 unstated-next 源碼》
- 🍑:React 輕量狀態管理庫 unstated-next 使用教程
求個 star,謝謝大家了