Redux 毫無疑問是眾多 React 專案首選的狀態管理方案,但我覺得 Redux 的開發體驗並不好。
比如當你正在開發一個很複雜的功能,中途需要不斷新增全域性狀態,每次新增都不得不重複如下步驟:
- 去到管理 redux 的資料夾,思考把這個狀態放到狀態樹的哪個位置,然後新建一個資料夾並命名
myFeature
。 - 建立三個檔案
my-feature/actions.js
、my-feature/reducer.js
、my-feature/type.js
- combineReducer 和並 reduce
- 將 action 引入到元件中
- 通過 connect HOC 與你的元件相連
- 增加兩個方法 mapStateToProps 和 mapDispatchToProps
以上只是加個狀態而已,寫很多模板程式碼還是其次,最要命的是會打斷你寫程式碼的思路。
而且隨著專案越來越大, redux 的狀態樹也會變大,維護也會變困難。
useContext + useReducer 如何替代 redux ?
useContext
和 useReducer
是 React 16.8 引入的新 API。
useContext
:可訪問全域性狀態,避免一層層的傳遞狀態。
useReducer
:用過 Redux 肯定不會陌生,它主要用於更新複雜邏輯的狀態。
下面通過一個簡單例子介紹 useContext + useReducer 是如何替代 Redux 的。
程式碼已放到 codesandbox,檢視完整程式碼
這個例子只有一個功能,點選按鈕改變字型顏色。
開始
首先用 create-react-app 建立一個專案,也可以在 CodeSandbox 上建立一個 React App。
建立顏色展示元件 ShowArea
import React from 'react'
const ShowArea = (props) => {
return (
<div style={{color: "blue"}}>字型顏色展示為blue</div>
)
}
export default ShowArea
複製程式碼
建立按鈕元件 buttons
import React from "react";
const Buttons = props => {
return (
<React.Fragment>
<button>紅色</button>
<button>黃色</button>
</React.Fragment>
);
};
export default Buttons;
複製程式碼
將 ShowArea 和 Buttons 匯入 index.js
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import ShowArea from './ShowArea'
import Buttons from './Buttons'
function App() {
return (
<div className="App">
<ShowArea />
<Button />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
複製程式碼
狀態管理
很明顯 ShowArea 元件需要一個顏色狀態,所以我們建立 color.js 來處理狀態。
// color.js
import React, { createContext } from "react";
// 建立 context
export const ColorContext = createContext({});
/**
* 建立一個 Color 元件
* Color 元件包裹的所有子元件都可以通過呼叫 ColorContext 訪問到 value
*/
export const Color = props => {
return (
<ColorContext.Provider value={{ color: "blue" }}>
{props.children}
</ColorContext.Provider>
);
};
複製程式碼
引入狀態
修改 index.js,讓所有子元件都可以訪問到顏色。
// index.js
···
···
···
import { Color } from "./color";
function App() {
return (
<div className="App">
<Color>
<ShowArea />
<Buttons />
</Color>
</div>
);
}
···
···
···
複製程式碼
獲取狀態
在 ShowArea 元件中獲取顏色
// ShowArea.js
···
···
···
import { ColorContext } from "./color";
const ShowArea = props => {
const { color } = useContext(ColorContext);
return <div style={{ color: color }}>字型顏色展示為{color}</div>;
};
···
···
···
複製程式碼
建立 reducer
接著在 color.js 中新增 reducer, 用於處理顏色更新的邏輯。
import React, { createContext, useReducer } from "react";
// 建立 context
export const ColorContext = createContext({});
// reducer
export const UPDATE_COLOR = "UPDATE_COLOR"
const reducer = (state, action) => {
switch(action.type) {
case UPDATE_COLOR:
return action.color
default:
return state
}
}
/**
* 建立一個 Color 元件
* Color 元件包裹的所有元件都可以訪問到 value
*/
export const Color = props => {
const [color, dispatch] = useReducer(reducer, 'blue')
return (
<ColorContext.Provider value={{color, dispatch}}>
{props.children}
</ColorContext.Provider>
);
};
複製程式碼
更新狀態
為按鈕新增點選事件,呼叫 dispatch 就可以更新顏色了。
// buttons.js
import React, { useContext } from "react";
import { colorContext, UPDATE_COLOR } from "./color";
const Buttons = props => {
const { dispatch } = useContext(colorContext);
return (
<React.Fragment>
<button
onClick={() => {
dispatch({ type: UPDATE_COLOR, color: "red" });
}}
>
紅色
</button>
<button
onClick={() => {
dispatch({ type: UPDATE_COLOR, color: "yellow" });
}}
>
黃色
</button>
</React.Fragment>
);
};
export default Buttons;
複製程式碼
總結
- useContext 建立全域性狀態,不用一層一層的傳遞狀態。
- useReducer 建立 reducer 根據不同的 dispatch 更新 state。
- 程式碼寫到哪裡狀態就加到哪裡,不用打斷思路跳到 redux 裡面去寫。
- 全域性狀態分離,避免專案變大導致 Redux 狀態樹難以管理。
所以 useContext + useReducer 完全可以替代 React 進行狀態管理。但是目前絕大多數 React 專案仍在使用 Redux,所以深入學習 Redux 還是很有必要的。