官方文档,视频教程
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
函数提供了一种快速创建 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 2 3 4
| dispatch({ type: "ADD", payload: 1 });
|
注意在onChange、onClick这样的变量里写的话,要写成 onChange={() => dispatch(add())}
subscribe()
Subscribe 是用于监听 Store 的变化,并在 Store 状态发生变化时触发回调函数。每当 Store 的状态发生变化时,Redux 会遍历所有已注册的监听器,然后逐一执行它们的回调函数。
Redux in React
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> ) }
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
|
背景:意外的发现好用,随着项目业务逐渐变复杂,我发现我需要大量写 … 扩展语句,使得代码冗长,不易读不易维护。于是我问 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