H1What is Redux?
H2Intro
Redux 是一种 JS 状态管理库,可用于任何 JS 应用程序,包括 React。Redux 提供单一的状态存储,可在整个应用程序中共享,方便跨组件共享数据。
在 Redux 中,状态存储在一个称为Store
的对象中,并且只能通过Action
来修改。Reducer
函数用于处理Action
,并返回新的状态对象。
它类似于 Context 概念,但 Context 并不像 Redux 那样提供状态管理和更新机制,你需要自己实现逻辑来修改 Context 中的数据。Redux 和 Context 是两种不同的技术,用于解决不同的问题,在一些小型应用或者组件层次较浅的应用中,使用 Context 可能会更加简单和方便。但在复杂应用中,Redux 的状态管理机制能够让你更方便地组织和管理应用的状态,也更容易调试和维护。
H2Workflow
Redux 中的数据流是单向的,从View
(视图)到Action
(动作)再到Reducer
(处理器)再到Store
(存储器),最终再次返回到View
中。当一个Action
被触发后,它会被传递到Reducer
中进行处理,Reducer
返回一个新的状态对象,Store
保存这个新的状态对象,然后通知View
进行更新。
H1Core concepts
为了形象理解概念,这将用餐厅举例:
Action 👉 Your Order
Dispatch 👉 Restaurant Server
Reducer 👉 Chef
Store 👉 Whole Restaurant
State 👉 它可以是餐厅内部的各种状态的集合,比如餐桌占用情况、食材库存等
getState 👉 获取餐厅当前的状态
Subscribe 👉 餐厅经理时刻关注/监视餐厅状态的变化,比如营业额、食材库存等
厨师(Reducer)处理服务员(Dispatch)传来的订单(Action)
H2Action
Action 是个普通 JS 对象,用于描述发生的事件和携带数据。也是应用状态的唯一来源,因为它们描述了应用中发生的所有事件,包括用户操作、网络响应等等。Action 常被用来触发状态的更新。
jsx
function changeCount(count) {
return {
type: "CHANGE_COUNT",
payload: count,
};
}
H3Property: type(必须)
type
用来描述 action 的类型。
书写要求:全大写,用下划线__来连接单词
H3Property: payload(可选)
payload
可以是任何值,用于携带与这个 action 相关的数据。
对于要存储多个值,可以考虑放入一个对象或者数组传进来,例如:
jsx
export function onFormChange(data) {
return {
type: "ON_FORM_CHANGE",
payload: {
section: section,
event: { name, value },
},
};
}
H2Reducer
Reducer 是个纯函数,用于处理 action 产生的数据,更新应用的状态。Reducer 接收一个 state 对象和一个 action 对象,然后根据 action 的 type 属性决定如何更新 state 对象。
注意,Reducer 需要通过返回一个新的 state 来更新状态,而不是直接修改原 state,因为 Redux 中的 state 是不可变的。
jsx
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;
}
}
H3configureStore()
configureStore
函数提供了一种快速创建 Redux store 的方式。
当应用变得越来越复杂时,你可能需要将 Reducer 拆分成更小的部分,每个部分负责管理全局 state 中的一个子集。这样做的好处是使 Reducer 更易于管理和测试,同时也可以更好地维护应用程序。最好将所有拆分的部分放在一个叫 redux
的文件夹。
redux/index.js
jsx
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
jsx
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
jsx
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;
}
}
H2Store
Store 是应用中存储 state 的地方,应用中的每一个组件都可以从 store 中获取状态,也可以向其添加新状态。在 Redux 中,Store 是唯一的,所有的状态都保存其中。
它还提供了一些方法来访问 state,如 getState 和 dispatch。
jsx
import redux, { createStore } from "redux";
//...
const store = createStore(reducer);
store.subscribe(() => {
console.log(store.getState());
});
store.dispatch(changeCount(2));
H3dispatch()
用于将 Action 提交给 Store,使得 Reducer 可以根据 Action 更新 state。
jsx
dispatch(add());
或
jsx
dispatch({
type: "ADD",
payload: 1,
});
注意在 onChange、onClick 这样的变量里写的话,要写成 onChange={() => dispatch(add())}
H3subscribe()
Subscribe 是用于监听 Store 的变化,并在 Store 状态发生变化时触发回调函数。每当 Store 的状态发生变化时,Redux 会遍历所有已注册的监听器,然后逐一执行它们的回调函数。
H1Redux in React (JS)
H2Install react-redux
react-redux 要下载,redux 也需要下。
bash
npm install react-redux
npm install @reduxjs/toolkit
H2<Provider />
jsx
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")
);
H2connect()
本质是 Higher Order Components(用不上,看后面介绍的两个 Hooks)
Syntax:
jsx
connect(states, actions)(Component);
connect(mapStateToPropsFunc, mapDispatchToPropsFunc)(Component);
H3mapStateToProps
"What parts of the state do you want?"
jsx
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)
H3mapDispatchToProps
"What actions do you wanna dispatch?"
jsx
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);
当属性和值的名称一样时,可以写成如下形式:
jsx
const mapDispatchToProps = {
increment,
decrement,
};
还有更简洁的写法(真叫一个专业对口):
jsx
import { increment, decrement } from "./redux";
export default connect(mapStateToProps, { increment, decrement })(App);
H2useSelector()
useSelector()
的作用是从 Redux Store 中获取所需的 state,并在组件中使用。
它的参数是一个函数,这个函数接收当前 Redux Store 中的 state 作为参数,并返回你需要使用的 state。
jsx
import { useSelector } from "react-redux";
function Counter() {
const count = useSelector((state) => state.count);
return (
<div>
<p>Count: {count}</p>
</div>
);
}
H2useDispatch()
useDispatch
用于在 React 组件中获取 dispatch
函数,以便触发 action 来更新 store 中的 state。
jsx
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>
);
}
H2Thunk
Thunk 是一种中间件,它允许我们在 action creator 中返回一个函数,而不是返回一个 action 对象。这个函数可以在内部进行异步操作,并在完成操作后再分发真正的 action。
Thunk 中间件会对每个分发的 action 进行检查,如果 action 是一个函数而不是一个对象,它就会执行这个函数,并将 dispatch 和 getState 作为参数传递给函数。这样,我们就可以在函数中进行异步操作,然后再根据异步操作的结果分发真正的 action。
jsx
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;
H1Redux in React (TS)
在
Next.js
里使用会报错,加载速度极慢,至少我不推荐在Next.js
用 redux。
/redux/store.ts
typescript
import { configureStore } from "@reduxjs/toolkit";
import themeSlice from "./themeSlice";
const store = configureStore({
reducer: {
themeConfig: themeSlice.reducer,
},
});
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch;
export default store;
/redux/hooks.ts
封装
useSelector
&useDispatch
,这样就不用每次调用时加 type。
typescript
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import type { RootState, AppDispatch } from "./store";
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
H1Useful Tools for Redux
H2Immer.js
场景:意外的发现好用,随着项目业务逐渐变复杂,我发现我需要大量写 … 扩展语句,使得代码冗长,不易读不易维护。于是我问 ChatGPT 后才认识到 Immer.js,不得不说 ChatGPT 真是藏得住哈哈哈,我问了它好多关于 Redux 的问题,那么长的代码它都不嫌复杂,还是我主动问它行业里有没有更优解,果然有。
Redux + Immer:
jsx
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