什么是状态管理
所谓状态管理,指的是把组件之间需要共享的状态抽取出来,遵循特定的约定,统一来管理,让状态的变化可以预测。
组件之间通常会有一些共享的状态,在 Vue 或者 React 中我们一般会将这部分状态提升至公共父组件的 props 中,由父组件来统一管理共享的状态,状态的改变也是由父组件执行并向下传递。这样会导致两个问题:
- 需要将共享的状态提升至公共的父组件,若无公共的父组件,往往需要自行构造
- 状态由父组件自上而下逐层传递,若组件层级过多,数据传递会变得很冗杂
此时,我们就需要一个统一的仓库来对组件状态进行管理,如下图:
Redux 的核心思想
早期的时候,React 官方提供了 Flux,Flux 的特点如下:
- 单向数据流。视图事件或者外部测试用例发出 Action ,经由 Dispatcher 派发给 Store ,Store 会触发相应的方法更新数据、更新视图
- Store 可以有多个
- Store 不仅存放数据,还封装了处理数据的方法
2015 年的时候,Dan Abramov 推出的 Redux 席卷了整个 React 社区。Redux是一个用于JavaScript应用程序的状态容器,用于管理应用程序的状态并进行预测性的状态修改。Redux 本质就是在 Flux 上做了一些更新。
Redux的核心概念是单一状态树,即整个应用程序的状态被存储在一个单一的JavaScript对象中。这个状态树是只读的,唯一改变状态的方式是通过触发一个称为action的对象,然后通过一个称为reducer的纯函数来处理这个action,并返回一个新的状态。Redux 官网:https://redux.js.org/。
Redux 特点
-
单一数据源:Redux 应用程序的整个状态存储在一个单一对象树中,称为 “Store”。这使得状态的管理和维护变得简单而可预测。
-
状态不可变性:Redux 要求状态是只读的,任何对状态的修改都必须通过创建新的状态对象来完成。这样做可以确保状态的可追踪性,并方便进行状态的比较和调试。
-
纯函数的状态更新:状态的更新通过纯函数来描述,这些函数被称为 “Reducers”。Reducers 接收当前状态和一个 “Action”,然后返回一个新的状态。这样可以确保状态的更新是可预测且可测试的。
-
中间件支持:Redux 支持使用中间件来扩展其功能,例如异步操作、日志记录等。中间件可以在发起一个 Action 到达 Reducer 之前对其进行拦截和处理。
-
强大的工具生态系统:Redux 生态系统提供了许多工具和插件,用于帮助开发者更好地调试、测试和优化 Redux 应用程序。
Redux 数据流:
-
Action:通过 dispatch 方法来触发 action,action 是一个包含 type 和 payload 的普通 JavaScript 对象,用来描述事件的具体操作。
-
Reducer:接收到 action 后,reducer 是根据 action 的类型来更新 state 的函数,它是一个纯函数,负责计算新的 state,并返回。
-
Store:store 是应用程序的核心,它负责存储整个应用的 state,并且提供了一些方法来访问和更新 state,包括 dispatch 方法用于触发 action,getState 方法用于获取当前的 state。
-
View:view 是用户与应用程序交互的界面,它可以通过监听 store 的变化来获取最新的 state,并根据 state 的变化来更新界面。
在 Redux 中,数据的流动是单向的,从 view 到 store,然后再到 reducer 更新 state,最后再返回给 view 更新界面。这种单向数据流的设计可以使得应用的状态管理更加可预测和可维护。
Redux 简单示例
安装
# NPM
npm install redux
# Yarn
yarn add redux
通过 createStore,可以创建一个 Store 仓库。createStore 需要接收 Reducer 函数。createStore 的 API 有 { subscribe, dispatch, getState }.
// store.js
import { createStore } from 'redux'
import { reducer } from './reducer.js'
export default createStore(reducer)
Reducer 函数:接受当前 state 值和描述“发生了什么”的 action 对象,它返回一个新的 state 值。
// reducer.js
import {INCREMENTED, DECREMENTED} from './actionType';
export function reducer(state = { value: 0 }, action) {
switch (action.type) {
case INCREMENTED:
return { value: state.value + 1 }
case DECREMENTED:
return { value: state.value - 1 }
default:
return state
}
}
action 用来描述事件的具体操作
import {INCREMENTED, DECREMENTED} from './actionType';
export const incremented = ()=>({ type: INCREMENTED })
export const decremented = ()=>({ type: DECREMENTED })
actionType 用来描述事件类型
// actionType.js
export const INCREMENTED = 'counter/incremented';
export const DECREMENTED = 'counter/decremented';
在根组件中注册store
// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
// 引入仓库
import store from "./store";
const root = ReactDOM.createRoot(document.getElementById('root'));
function render(){
root.render(
<App store={store}/>
);
}
render();
subscribe(render)
在组件中获取 state
function App(prop) {
const state = prop.store.getState()
}
在组件中派发更新
import {DECREMENTED} from './actionType';
function App(prop) {
prop.store.dispatch({ type: DECREMENTED })
}
在典型的 Redux 应用程序中,只有一个 store 以及一个根 reducer 函数。随着应用程序的增长,您可以将根 reducer 拆分为较小的 reducer,分别在状态树的不同部分上进行运行。这就像在 React 应用程序中只有一个根组件一样,但是它是由许多小组件组成的。
如何使用 redux devtools
在浏览器中使用 redux 扩展工具,首先需要打开谷歌浏览器的扩展应用商店,下载 redux devtools
接下来需要在创建 store 的地方,添加如下的代码:
export const store = createStore(
todoReducer,
+ window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
详细可以参阅:https://github.com/zalmoxisus/redux-devtools-extension#usage
React-redux 介绍
Redux 是一个独立的第三方库,之后 React 官方在 Redux 的基础上推出了 React-redux:https://react-redux.js.org/
最新版的 React-redux,已经全面拥抱了 Hooks,内置了诸如:
- useSelector
- useDispatch
- useStore
一类的 Hook,我们只要掌握这一类 Hook,就可以轻松上手。
另外,Redux 官方还推出了 Redux Toolkit,来简化整个 Redux 的使用。官方文档:https://redux-toolkit.js.org/
因此现在在 React 应用中,状态管理库的使用一般都是 React-redux + Redux Toolkit
React-redux + Redux Toolkit 示例:ToDoList
- 安装
npm install @reduxjs/toolkit react-redux
- 使用 @reduxjs/toolkit configureStore 方法 创建 store 仓库
// store.js
import { configureStore } from '@reduxjs/toolkit'
import todosReducer from './todosSlice'
export default const store = configureStore({
reducer: {
todos: todosReducer,
}
})
configureStore 通过单个函数调用设置一个配置完善的 Redux store,包括合并 reducer、添加 thunk 中间件以及设置 Redux DevTools 集成。
- 创建 Redux State Slice
// todosSlice.js
import { createSlice } from '@reduxjs/toolkit'
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
increment: state => {
// Redux Toolkit 允许我们在 reducers 写 "可变" 逻辑。它
// 并不是真正的改变状态值,因为它使用了 Immer 库
// 可以检测到“草稿状态“ 的变化并且基于这些变化生产全新的
// 不可变的状态
state.value += 1
},
decrement: state => {
state.value -= 1
},
incrementByAmount: (state, action) => {
state.value += action.payload
}
}
})
// 每个 case reducer 函数会生成对应的 Action creators
export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer
export const { todoAdded, todoToggled } = todosSlice.actions
export default todosSlice.reducer
createSlice 使用 Immer 库 来编写 reducer,可以使用 “mutating” JS 语法,比如 state.value = 123,不需要使用拓展运算符。 内部基于你的 reducer 名称生成 action type 字符串。也可以支持 TypeScript 。
- 为 React 提供 Redux Store
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import store from './store'
import { Provider } from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
5.在 React 组件中使用 Redux 状态和操作
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment } from './counterSlice'
import styles from './Counter.module.css'
export function Counter() {
const count = useSelector(state => state.counter.value)
const dispatch = useDispatch()
return (
<div>
<div>
<button
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
Increment
</button>
<span>{count}</span>
<button
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
Decrement
</button>
</div>
</div>
)
}
6.如何在React组件中派发异步action
- 在 todosSlice 使用@reduxjs/toolkit createAsyncThunk 创建异步的 action
// todosSlice.js
import { createAsyncThunk } from "@reduxjs/toolkit";
export const getStuListAsync = createAsyncThunk(
"stu/getStuListAsync",
async (_, thunkApi) => {
// 发送 ajax 请求
const response = await getStuListApi();
// 派发 action
thunkApi.dispatch(increment());
}
);
- 在React组件中使用 dispatch 派发异步 action
getStuListAsync
总结
总的来说,React-redux和Redux Toolkit在开发React应用中使用Redux变得更加简单和高效。React-redux提供了与React组件连接的功能,而Redux Toolkit则提供了一些工具和约定,使得Redux代码更易于编写和维护。使用React-redux和Redux Toolkit可以加速开发过程,并且减少代码量和维护的复杂性。