官方文档视频教程

What is Redux?

Intro

Redux 是一种 JS 状态管理库,可用于任何 JS 应用程序,包括 React。Redux 提供单一的状态存储,可在整个应用程序中共享,方便跨组件共享数据。

在Redux中,状态存储在一个称为Store的对象中,并且只能通过Action来修改。Reducer函数用于处理Action,并返回新的状态对象。

它类似于 Context 概念,但 Context 并不像 Redux 那样提供状态管理和更新机制,你需要自己实现逻辑来修改 Context 中的数据。Redux 和 Context 是两种不同的技术,用于解决不同的问题,在一些小型应用或者组件层次较浅的应用中,使用 Context 可能会更加简单和方便。但在复杂应用中,Redux 的状态管理机制能够让你更方便地组织和管理应用的状态,也更容易调试和维护。

Workflow

Redux中的数据流是单向的,从View(视图)到Action(动作)再到Reducer(处理器)再到Store(存储器),最终再次返回到View中。当一个Action被触发后,它会被传递到Reducer中进行处理,Reducer返回一个新的状态对象,Store保存这个新的状态对象,然后通知View进行更新。

Core concepts

为了形象理解概念,这将用餐厅举例:

Action 👉 Your Order

Dispatch 👉 Restaurant Server

Reducer 👉 Chef

Store 👉 Whole Restaurant

State 👉 它可以是餐厅内部的各种状态的集合,比如餐桌占用情况、食材库存等

getState 👉 获取餐厅当前的状态

Subscribe 👉 餐厅经理时刻关注/监视餐厅状态的变化,比如营业额、食材库存等

厨师(Reducer)处理服务员(Dispatch)传来的订单(Action)

Action

Action 是个普通 JS 对象,用于描述发生的事件携带数据。也是应用状态的唯一来源,因为它们描述了应用中发生的所有事件,包括用户操作、网络响应等等。Action 常被用来触发状态的更新。

1
2
3
4
5
6
function changeCount(count) {
return {
type: 'CHANGE_COUNT',
payload: count
}
}

Property: type(必须)

type 用来描述 action 的类型。

书写要求:全大写,用下划线__来连接单词

Property: payload(可选)

payload 可以是任何值,用于携带与这个 action 相关的数据。

对于要存储多个值,可以考虑放入一个对象或者数组传进来,例如:

1
2
3
4
5
6
7
8
9
export function onFormChange(data) {
return {
type: "ON_FORM_CHANGE",
payload: {
section: section,
event: { name, value },
},
};
}

Reducer

Reducer 是个纯函数,用于处理 action 产生的数据,更新应用的状态。Reducer 接收一个 state 对象和一个 action 对象,然后根据 action 的 type 属性决定如何更新 state 对象。

注意,Reducer 需要通过返回一个新的 state 来更新状态,而不是直接修改原 state,因为 Redux 中的 state 是不可变的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const initialState = {
count: 0,
favoriteThings: []
}

function reducer(state = initialState, action) {
switch(action.type) {
case "CHANGE_COUNT":
return {
...state,
count: state.count + action.payload
}
case "ADD_FAVORITE_THING":
return {
...state,
favoriteThings: [...state.favoriteThings, action.payload]
}
default:
return state
}
}

configureStore()

configureStore 函数提供了一种快速创建 Redux store 的方式。

当应用变得越来越复杂时,你可能需要将 Reducer 拆分成更小的部分,每个部分负责管理全局 state 中的一个子集。这样做的好处是使 Reducer 更易于管理和测试,同时也可以更好地维护应用程序。最好将所有拆分的部分放在一个叫 redux 的文件夹。

redux/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { configureStore } from "@reduxjs/toolkit"
import countReducer from "./count"
import favoriteThingsReducer from "./favoriteThings"

const store = configureStore({
reducer: {
count: countReducer,
favoriteThings: favoriteThingsReducer,
}
})

store.subscribe(() => {
console.log(store.getState())
})

export default store

redux/count.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export function changeCount(amount = 1) {
return {
type: "CHANGE_COUNT",
payload: amount
}
}

export default function countReducer(count = 0, action) {
switch(action.type) {
case "CHANGE_COUNT":
return count + action.payload
default:
return count
}
}

redux/favoriteThings.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
export function addFavoriteThing(thing) {
return {
type: "ADD_FAVORITE_THING",
payload: thing
}
}

export function removeFavoriteThing(thing) {
return {
type: "REMOVE_FAVORITE_THING",
payload: thing
}
}

export default function favoriteThingsReducer(favoriteThings = [], action) {
switch(action.type) {
case "ADD_FAVORITE_THING":
return [...favoriteThings, action.payload]
case "REMOVE_FAVORITE_THING": {
const updatedArr = favoriteThings.filter(thing => thing.toLowerCase() !== action.payload.toLowerCase())
return updatedArr
}
default:
return favoriteThings
}
}

Store

Store 是应用中存储 state 的地方,应用中的每一个组件都可以从 store 中获取状态,也可以向其添加新状态。在Redux中,Store 是唯一的,所有的状态都保存其中。

它还提供了一些方法来访问 state,如 getState 和 dispatch。

1
2
3
4
5
6
7
8
9
import redux, {createStore} from "redux"
//...
const store = createStore(reducer)

store.subscribe(() => {
console.log(store.getState())
})

store.dispatch(changeCount(2))

dispatch()

用于将 Action 提交给 Store,使得 Reducer 可以根据 Action 更新 state。

1
dispatch(add());

1
2
3
4
dispatch({
type: "ADD",
payload: 1
});

注意在onChange、onClick这样的变量里写的话,要写成 onChange={() => dispatch(add())}

subscribe()

Subscribe 是用于监听 Store 的变化,并在 Store 状态发生变化时触发回调函数。每当 Store 的状态发生变化时,Redux 会遍历所有已注册的监听器,然后逐一执行它们的回调函数。

Redux in React

install react-redux

react-redux 要下载,redux 也需要下。

npm install react-redux

npm install @reduxjs/toolkit

<Provider />

1
2
3
4
5
6
7
8
9
10
11
12
13
import React from "react"
import ReactDOM from "react-dom"
import {Provider} from "react-redux"
import store from "./redux"

import App from "./App"

ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
)

connect()

本质是 Higher Order Components(用不上,看后面介绍的两个 Hooks)

Syntax:

1
2
connect(states, actions)(Component)
connect(mapStateToPropsFunc, mapDispatchToPropsFunc)(Component)

mapStateToProps

"What parts of the state do you want?"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React from "react"
import {connect} from "react-redux"

function App(props) {
return (
<div>
<h1>{props.count}</h1>
<button>-</button>
<button>+</button>
</div>
)
}

function mapStateToProps(state) {
return {
count: state
}
}

export default connect(mapStateToProps, {})(App)

更简洁的写法:

export default connect(state => ({count: state}), {})(App)

mapDispatchToProps

"What actions do you wanna dispatch?"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React from "react"
import {connect} from "react-redux"
import {increment, decrement} from "./redux"

function App(props) {
return (
<div>
<h1>{props.count}</h1>
<button onClick={props.decrement}>-</button>
<button onClick={props.increment}>+</button>
</div>
)
}
// mapStateToProps ...
const mapDispatchToProps = {
increment: increment,
decrement: decrement
}

export default connect(mapStateToProps, mapDispatchToProps)(App)

当属性和值的名称一样时,可以写成如下形式:

1
2
3
4
const mapDispatchToProps = {
increment,
decrement
}

还有更简洁的写法(真叫一个专业对口):

1
2
3
import {increment, decrement} from "./redux"

export default connect(mapStateToProps, {increment, decrement})(App)

useSelector()

useSelector() 的作用是从 Redux Store 中获取所需的 state,并在组件中使用。

它的参数是一个函数,这个函数接收当前 Redux Store 中的 state 作为参数,并返回你需要使用的 state。

1
2
3
4
5
6
7
8
9
10
11
import { useSelector } from 'react-redux';

function Counter() {
const count = useSelector(state => state.count);

return (
<div>
<p>Count: {count}</p>
</div>
);
}

useDispatch()

useDispatch 用于在 React 组件中获取 dispatch 函数,以便触发 action 来更新 store 中的 state。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from "react"
import {useSelector, useDispatch} from "react-redux"
import {increment, decrement} from "./redux"

function App(props) {
const count = useSelector(state => state.count);
const dispatch = useDispatch();
return (
<div>
<h1>{count}</h1>
<button onClick={() => dispatch(decrement())}>-</button>
<button onClick={() => dispatch(increment())}>+</button>
</div>
)
}

Thunk

Thunk 是一种中间件,它允许我们在 action creator 中返回一个函数,而不是返回一个 action 对象。这个函数可以在内部进行异步操作,并在完成操作后再分发真正的 action。

Thunk 中间件会对每个分发的 action 进行检查,如果 action 是一个函数而不是一个对象,它就会执行这个函数,并将 dispatch 和 getState 作为参数传递给函数。这样,我们就可以在函数中进行异步操作,然后再根据异步操作的结果分发真正的 action。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import redux, {createStore, applyMiddleware} from "redux"
import thunk from "redux-thunk"

export function increment() {
return (dispatch, getState) => {
const currentCount = getState()
if(currentCount % 2 === 0) {
dispatch({type: "INCREMENT"})
} else {
setTimeout(() => {
dispatch({type: "INCREMENT"})
}, 1500)
}
}
}

function reducer(count = 0, action) {
switch(action.type) {
case "INCREMENT":
return count + 1
default:
return count
}
}

const store = createStore(reducer, applyMiddleware(thunk))
store.subscribe(() => console.log(store.getState()))
export default store

Useful Tools for Redux

Immer.js

背景:意外的发现好用,随着项目业务逐渐变复杂,我发现我需要大量写 … 扩展语句,使得代码冗长,不易读不易维护。于是我问 ChatGPT 后才认识到 Immer.js,不得不说 ChatGPT 真是藏得住哈哈哈,我问了它好多关于 Redux 的问题,那么长的代码它都不嫌复杂,还是我主动问它行业里有没有更优解,果然有。

Redux + Immer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import {produce} from "immer"

export const onFormChange = (section, name, value) => {
return {
type: "ON_FORM_CHANGE",
payload: {
section: section,
name: name,
value: value,
},
};
};

initialState = {...}

const formDataReducer = produce((formData = initialState, action) => {
let section, name, value;
switch (action.type) {
case "ON_FORM_CHANGE":
({ section, name, value } = action.payload);
formData[section][name] = value;
break;
default:
return formData;
}
}

export default formDataReducer;

它不仅可以用在 Redux 里还可以用在 useState 里。

推荐文章:Using Immer with React: a Simple Solutions for Immutable States