前言
为什么不用Redux
一句话概括就是使用Redux太麻烦了:
- 比较适合中大型项目
- 添加一次全局状态的过程及其麻烦且容易出错
- 当
Redux状态树变多时,维护更加困难
React Hook了解一下
自从React发布了React Hook后,大家就想到了使用useContext和useReducer来模拟Redux
好处是什么:
- 可以和
Redux分离使用,只需在用到的地方添加状态,相当于一个局部的全局状态。 - 适合小型项目或个人学习
React时开发 - 不用写过多的代码,易于后续维护
先分别介绍一下useContext和useReducer分别是做什么的:
useContext
要使用useContext之前,我们需要先知道Context是什么,官方文档里面有说明
Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。
也就是说,使用Context来管理我们的“全局”状态是再好不过的了。
而useContext就是为了接收context对象并返回context的值而存在的。说白了就是我们可以通过useContext来访问全局状态Context。
useReducer
我们先来看一下useReducer的函数。
const [state, dispatch] = useReducer(reducer, initialArg, init);
它接收一个如(state, action) => newState的reducer,并返回当前的state以及预期配套的dispath方法。(用过Redux 应该不会陌生)官方文档
开始实践
下面我们用一个简单的例子通过useContext和useReducer来实现Redux的效果。
该例子使用实现主题色切换来管理全局状态
初始化项目
你可以通过create-react-app创建一个React项目进行测试,也可以在CodeSandbox进行模拟
笔者用的是TypeScript进行项目编写的,你也可以通过
npx create-react-app my-app(项目名) --typescript
# 或者
yarn create react-app my-app(项目名) --typescript
创建一个TypeScript的React项目
创建两个子组件
创建好项目后,在src目录下新建一个pages
pages内分别创建switch和theme两个组件,这里只列出主要目录结构
.
├── src
| └── pages
| | ├── switch
| | | └── index.tsx // 用来修改theme状态
| | └── state
| | | └── index.tsx // 用来显示theme状态
└── store
└── theme.tsx // 用来存储theme状态
创建State组件(用来显示theme状态)
index.tsx
import React from 'react'
const State: React.FC = () => {
return (
<div>由我显示全局变量</div>
)
}
export default State;
创建Switch组件(用来修改theme状态)
index.tsx
import React from 'react'
const Switch: React.FC = () => {
return (
<section>
<button>我是改变状态1</button>
<button>我是改变状态2</button>
</section>
)
}
export default Switch;
现在我们把这两个组件都引入到App.tsx内
import React from 'react';
import Switch from './pages/switch'
import State from './pages/state'
import './App.css';
const App: React.FC = () => {
return (
<div className="App">
<Switch></Switch>
<State></State>
</div>
);
}
export default App;
此时我们运行我们的项目,可以看到页面大概是这样的

此时我们第一步就算完成了
状态管理
在src目录下创建一个store目录文件,新建一个文件,名为theme.tsx
theme.tsx
初始化主题值
import React, { createContext, Context } from 'react'
// 定义主题色的接口
interface ITheme {
theme: string
}
// 初始化
export const initialTheme: ITheme = {
theme: '等待改变主题'
}
// 创建一个Context实例
export const ThemeContext: Context<any> = createContext(initialTheme);
/**
* 创建一个 Theme 组件
* Theme 组件包裹的所有子组件都可以通过调用 ThemeContext 访问到 value
*/
export const Theme: React.FC = (props) => {
return (
<ThemeContext.Provider value={{state: initialTheme}}>
{props.children}
</ThemeContext.Provider>
)
}
这里有个重点就是,当ThemeContext.Provider的value值发生变化时,它内部的所有子组件都会重新渲染。
所以我们需要将Theme组件包裹在State和Switch的外层
此时App.tsx变为
import React from 'react';
import Switch from './pages/switch'
import State from './pages/state'
import { Theme } from './store/theme'
import './App.css';
const App: React.FC = () => {
return (
<div className="App">
<Theme>
<Switch></Switch>
<State></State>
</Theme>
</div>
);
}
export default App;
此时我们第二步就算完成了
State读取状态(useContext)
现在我们可以在State组件内通过useContext来读取Theme的状态了
// 引入useContext
import React, { useContext } from 'react'
// 引入Theme的Context
import { ThemeContext } from '../../store/theme'
const State: React.FC = () => {
// 获取Theme传来的value值
const { state } = useContext(ThemeContext)
return (
<div className="theme light">{state.theme}</div>
)
}
export default State;
此时运行我们的项目,发现State已经读取到了Theme的值,说明我们的第三步已经成功了

改变状态(useReducer)
接下来我们需要通过Switch内的两个按钮来改变Theme的值,看下State组件内的状态有没发生变化
在Theme内添加reducer
在Theme组件内使用useReducer并且添加reducer方法用于修改theme的状态
import React, { createContext, Context, useReducer } from 'react'
// 定义主题色的接口
interface ITheme {
theme: string
}
// 初始化
export const initialTheme: ITheme = {
theme: '等待改变主题'
}
// 创建一个Context实例
export const ThemeContext: Context<any> = createContext(initialTheme);
// (新增)初始化store的类型、初始化值、reducer
export const CHANGE_THEME: string = 'CHANGE_THEME';
// (新增)编写reducer函数
export const reducer = (state: ITheme, action: any) => {
switch (action.type) {
case CHANGE_THEME:
return { ...state, theme: action.theme }
default:
throw new Error();
}
}
/**
* 创建一个 Theme 组件
* Theme 组件包裹的所有子组件都可以通过调用 ThemeContext 访问到 value
*/
export const Theme: React.FC = (props) => {
// 通过使用useReducer更新状态
const [state, dispatch] = useReducer(reducer, initialTheme);
return (
<ThemeContext.Provider value={{state, dispatch}}>
{props.children}
</ThemeContext.Provider>
)
}
此时我们的Theme组件已全部编写完毕。
Switch添加事件
我们Switch也通过useContext来读取Theme的状态,然后在button内添加修改状态的dispatch,便可以修改Theme的状态了
修改后的Swtich组件
import React, { useContext } from 'react'
import { ThemeContext, CHANGE_THEME } from '../../store/theme'
const Switch: React.FC = () => {
// 调用dispatch修改状态
const { dispatch } = useContext(ThemeContext)
return (
<section>
<button
onClick={() => {
dispatch({ type: CHANGE_THEME, theme: "Change One" });
}}>
我是改变状态1
</button>
<button
onClick={() => {
dispatch({ type: CHANGE_THEME, theme: "Change Two" });
}}>
我是改变状态2
</button>
</section>
)
}
export default Switch;
运行项目效果如下

至此,所有步骤完成。
demo已传到了GitHub,可以和你的代码进行对比😜
结尾
写这篇文章的初衷就是觉得在React中使用一次Redux真的太麻烦了,有了React Hook后的确少了不少重复的操作,希望能分享给大家。记得给我点个赞喔,算是对我一种鼓励吧,哈哈!