Post's cover

官方文档视频教程

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 常被用来触发状态的更新。

jsxfunction changeCount(count) { return { type: "CHANGE_COUNT", payload: count, }; }

H3Property: type(必须)

type 用来描述 action 的类型。

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

H3Property: payload(可选)

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

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

jsxexport 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 是不可变的。

jsxconst 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

jsximport { 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

jsxexport 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

jsxexport 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。

jsximport redux, { createStore } from "redux"; //... const store = createStore(reducer); store.subscribe(() => { console.log(store.getState()); }); store.dispatch(changeCount(2));

H3dispatch()

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

jsxdispatch(add());

jsxdispatch({ 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 也需要下。

bashnpm install react-redux npm install @reduxjs/toolkit

H2<Provider />

jsximport 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:

jsxconnect(states, actions)(Component); connect(mapStateToPropsFunc, mapDispatchToPropsFunc)(Component);

H3mapStateToProps

"What parts of the state do you want?"

jsximport 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?"

jsximport 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);

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

jsxconst mapDispatchToProps = { increment, decrement, };

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

jsximport { increment, decrement } from "./redux"; export default connect(mapStateToProps, { increment, decrement })(App);

H2useSelector()

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

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

jsximport { 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。

jsximport 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。

jsximport 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。

Doc: Usage with Typescript

/redux/store.ts

typescriptimport { 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。

typescriptimport { 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:

jsximport {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

Prev

Next

Related Posts