前言
为什么不用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
后的确少了不少重复的操作,希望能分享给大家。记得给我点个赞喔,算是对我一种鼓励吧,哈哈!