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"}}>字体颜色展示为bluediv>
)
}
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 还是很有必要的。
参考
- React Context vs Redux - Who wins?
- React Context API simple tutorial. Does it replace Redux? + useContext, useReducer hooks
- Provide more ways to bail out inside Hooks
- Preventing rerenders with React.memo and useContext hook
- Don’t use Redux!