注: 此文章立場不表示 Hooks 能夠徹底代替 Redux。
由於 Redux 還有其餘適用的場景和功能,只是在大部分場景能夠用 Hooks 代替。
理性選擇即合理。react
React Hooks 面世也有很大一段時間了,我相信不少人對於 Hooks 的認知還大概處在:數據庫
咱們知道 React 是一個以構建 UI 爲主的的庫: A JavaScript library for building user interfaces. 可是 UI 若是脫離了數據,基本上也就是耍流氓了。 因此有 Redux、Mbox… 這樣以數據管理爲核心的庫出現了。 現實業務場景中,UI 與數據相輔相成。npm
在我最初學 React 的時候,原於成熟的方案、同事的推薦,是直接和 Redux 一塊兒學習而且上手開發的。 當時我就在想:React 爲何不能本身實現相似 Redux 那樣的數據處理功能呢? 對於想學習 React 的同窗,無疑是增長了 Redux 的學習成本, 更加深了 React 的門檻與神祕值「這可不是一個優秀的開源庫該有的特質」。編程
往簡單了說 Redux 就是實現了全局 state 、處理全局 state 的方式和統一的數據處理中心,也就是 store、dispatch 和 reducer。 雖然在 Hooks 以前咱們能夠經過 Context 模擬全局 state,可是咱們還不能優雅的模擬 dispatch、reducer。redux
若是 React 官方能出一個數據處理的解決方案, 不僅僅是減小一個 Redux npm 包的 bundle 體積, 還下降了學習與構建 React 應用的成本, 最重要的是更統一化的數據處理思想。數組
年前,我在構建一個新的後臺管理應用,考慮使用全新的 Hooks API。 當時 React 最新的版本仍是 16.7.0-alpha.2。 在對於數據處理上,我嘗試了新的 React Context API, 使用 Context API 提供的 Provider 和 Consumer 的方法,去實現代替 Redux 的數據處理方案「這也是網上大部分推薦的代替 Redux 的方案」。 可是代碼越寫越多,數據處理量愈來愈大,數據分類愈來愈多的時候,Context 顯得力不從心, 雖然能解決需求,可是代碼組織方式已經亂成了一鍋粥「嘗試過這個方案的人,應該知道我在說什麼」。bash
注:更不要使用 useState + context 的方式建立全局倉庫來代替 Redux數據結構
十分萬幸的是,不久後 React 更新版本到 16.8.1。 推出了新的 Hooks:useReducer,驚喜以外意料之中。 這也就是這篇文章要講的核心:使用 Hooks:useReducer 代替 Redux。dom
redux數據庫設計
hooksredux 的數據流程圖畫得比較簡單,理解大概意思就好,畢竟它不是我要說的重點, 和 hooks 的數據流程相比實際上是大同小異。
從 hooks 數據流能大體看出來, 咱們設計好 store 後,經過對應的 hooks 函數生成每一個 store 的 Provider 和 Context。 咱們把全部的單個 Provider 糅合爲一個總體的 Providers,做爲全部 UI 的父組件。
在任何一個子 UI 組件內部,經過 hooks 函數獲得對應 store 的 state、dispatch。 UI 組件內,經過主動調用 dispatch 發送 action,而後通過 store 的數據處理中心 reducer,就能觸發相應的數據改變。 這個數據流程和 redux 幾乎如出一轍。
在構建應用以前,咱們應該充分了解咱們的應用,瞭解每個 API 接口和返回的數據。這樣不至於開發中期再來修改咱們的倉庫設計。 須要咱們設計一個本地數「全局 store」,和相應的 action 用來修改這些數據。 其次就是目錄設計了。 接下來咱們以一個 TO DO Lists 爲例開發一個純 hooks 的 SPA 吧。
index.js 應用入口
...
import List from 'Pages/List/index';
import Layout from 'Components/Layout/index';
import history from './history';
ReactDOM.render(
<Router history={history}>
<Layout>
<Switch>
<Redirect exact from="/" to="/List" />
<Route path="/list" component={List} />
<Route component={NotFound} />
</Switch>
</Layout>
</Router>,
document.getElementById('container')
);
複製代碼
components/Layout/index 應用主結構
...
// 引入組合 Provider
import Provider from 'Store/provider';
import Header from './components/Header/index';
import Footer from './components/Footer/index';
const Layout = (props) => (
<Provider>
<Header />
{props.children}
<Footer />
</Provider>
);
export default Layout;
複製代碼
這裏的代碼很關鍵,在 Layout 中咱們引入「組合 Provider」, 提供「統一倉庫數據提供」的能力,讓子 UI 組件能獲取 store 數據。
...
import Lists from './lists/index';
const Provider = (props) => {
return (
<Lists.Provider>
{props.children}
</Lists.Provider>
);
};
export default Provider;
複製代碼
仔細觀察這裏的代碼人應該會發現一個問題: 在 store 拓展的的狀況下,這個代碼極可能出現 代碼嵌套地獄,相似這樣:
...
// 多個 store 實例的狀況
import One from './One/index';
import Two from './Two/index';
import Three from './Three/index';
...
const Provider = (props) => {
return (
<One.Provider>
<Two.Provider>
<Three.Provider>
...
{props.children}
...
</Three.Provider>
</Two.Provider>
</One.Provider>
);
};
export default Provider;
複製代碼
因此須要 provider 組合器。
優化後 provider.js
...
import providers from './providers';
// 數據 Provider 組合器
const ProvidersComposer = (props) => (
props.providers.reduceRight((children, Parent) => (
<Parent>{children}</Parent>
), props.children)
);
const Provider = (props) => {
return (
<ProvidersComposer providers={providers}>
{props.children}
</ProvidersComposer>
);
};
export default Provider;
複製代碼
providers.js
import Lists from './lists/index';
const providers = [Lists.Provider];
export default providers;
複製代碼
即便有多個 Provider 咱們也能夠經過一維數組搞定啦!
數據項 && 數據處理器
在構建好基本的 Provider 後,咱們須要提供基本的數據項和 reducer。
數據項
import React, { useReducer } from 'react';
import reducer, { initState } from './reducer';
const Context = React.createContext();
const Provider = (props) => {
const [state, dispatch] = useReducer(reducer, initState);
return (
<Context.Provider value={{ state, dispatch }}>
{props.children}
</Context.Provider>
);
};
export default { Context, Provider };
複製代碼
首先使用 createContext 函數建立好上下 Context,而且對 Context 的 Provider 提供初始化 value,即 state、dispatch。 初始化的 state、dispatch 來自於 hooks:useReducer: 經過 useReducer 函數傳入 reducer、initState,獲得這樣的數據結構: [state, dispatch]。
不一樣的數據項的代碼徹底是通用,差別點在於每一個數據項的 reducer、initState 不同。 reducer
export const initState = []; // 默認 todolist 是空數組
// 數據處理器
const reducer = (state, action) => {
const { type, payload } = action;
switch (type) {
case 'ADD':
return [...state, payload.data];
case 'MODIFY':
return state.map(
item => (item.id === payload.id ? payload.data : item);
case 'DELETE':
return state.map(
item => (item.id === payload.id ? null : item)
).filter(n => n);
default:
return state;
}
};
export default reducer;
複製代碼
能看出來,hooks reduer 和 redux reducer 基本沒有區別。 咱們根據 action 更新 state,仍是那麼熟悉的味道,那麼熟悉的 switch 函數。
UI 組件
import React from 'react';
import StoreLists from 'Store/lists/index';
const Lists = () => {
const { state: lists, dispatch: listsDispatch } = React.useContext(StoreLists.Context);
const { newList, setNewList } = React.useState('');
handleDelete = item => () => {
listsDispatch({
type: 'DELETE',
payload: item
});
};
handleSetNewList = e => {
setNewList(e.value);
};
handleNewList = () => {
listsDispatch({
type: 'ADD',
payload: {
id: Math.random() * 100,
name: newList
}
});
};
return (
<div>
<h1>TO DO list 列表</h1>
<ul>
{lists.map(item => (
<li key={item.id}>
{item.name}
<button onClick={handleDelete(item)}>刪除</button>
</li>
))}
</ul>
<inpt type="text" value={newList} onChange={handleSetNewList} />
<button onChange={handleNewList}>新建列表</button>
</div>
);
};
export default Lists;
複製代碼
在 UI 組件內,使用 hooks:useContext。 useContext 接受 store 導出的 Context 做爲參數,獲得 state、dispatch。 使用 state 渲染數據,使用 dispatch 修改數據。
經過上面的目錄結構、store 設計、UI 組件三大步驟,咱們可使用 hooks 搭建出和 redux 同樣的數據處理流程應用了。 若是想進一步瞭解,能夠參考應用:tw-agents。
文章轉載地址: fed.taobao.org/blog/2019/0…