找回密码
 立即注册
首页 业界区 业界 Redux 状态容器、管理

Redux 状态容器、管理

昝沛珊 2025-7-30 15:46:48
当你越来越有能力时,自然会有人看得起你;改变自己,你才有自信,梦想才会慢慢的实现。喷泉之所以漂亮是因为她有了压力;瀑布之所以壮观是因为她没有了退路;水之所以能穿石是因为永远在坚持。
首先我们要明确一个 React 组件,它与数据挂钩的只有 props 和 state,一个是从上级传下来的数据,一个是内部的状态,只能向下传,不能直接向上传。
这样的话,我们如何处理同级别组件的通信呢?一个最直接的方式就是创建一个最顶层的 state,把数据当作 props 向下传。这个最顶层的存放 state 的地方,我们可以认为是 store。
1. Flux 和 Redux

Flux 是一种架构思想,专门解决软件的结构问题,它跟 MVC 架构是同一类东西,但是更加简单和清晰。Flux 存在至少多种实现,比如:Redux。
https://github.com/voronianski/flux-comparison
Facebook Flux 是用来构建客户端 Web 应用的应用架构。利用单向数据流的方式来组合 React 中的视图组件。它更像一个模式而不是一个正式的框架,开发者不需要太多的新代码就可以快速的上手 Flux。
中心思想流程:

  • 用户访问 View,View 订阅 Store;
  • View 发出用户的 Action;
  • Dispatcher 收到 Action,要求 Store 进行相应的更新;
  • Store 更新后,发出一个 Change 事件;
  • View 收到 Change 事件后,更新页面;
1.png

Redux 最主要是用作应用状态的管理。简言之,Redux 用一个单独的常量状态树(state对象),保存这一整个应用的状态,这个对象不能直接被改变,当一些数据变化了,一个新的对象就会被创建(使用 actions 和 reducers)这样就可以进行数据追踪,实现时光旅行。
2. Redux 工作流

Redux 原理就是订阅、发布模式。我们看其中主要的几个重要的概念:

  • 组件 Component
  • Actions:用户系统行为,Actions Creators 创建;
  • Store:state 以单一对象存储在 store 中:

    • 状态调度:Store.dispatch(action);
    • 状态展示:Store.getState();
    • 订阅改变: Store.subscribe();

  • Reducers:纯处理函数 reducer,里面对老状态处理,得到新状态;

点击组件按钮触发一个事件,通过 Action Creator 创建一个 action 对象。通过 dispatch 把这个 action 对象发送到 store 里面。在 store 里面 需要通过 reducers 来更新状态。store 自己是无法更新状态的。reducers 必须接收老的对象和 action,然后根据 action 的 type 不同进行处理,返回新的状态,新的状态更新了,store 就会通知那些订阅者组件进行更新。
Redux store 默认将数据存储在内存中,因此‌页面刷新后数据会丢失‌。这是因为浏览器刷新会重新加载整个应用,内存中的状态会被重置‌。
Action 文件:
  1. var increment = () => {
  2.   return {
  3.     type: "increment"
  4.   }
  5. }
  6. var decrement = () => {
  7.   return {
  8.     type: "decrement"
  9.   }
  10. }
  11. export { increment, decrement }
复制代码
Store 文件:
  1. import { createStore } from "@reduxjs/toolkit";
  2. import counterReducer from "./reducer";
  3. const Store = createStore(counterReducer)
  4. export default Store
复制代码
Reducer 文件:
  1. const counterReducer = (state, action) => {
  2.   if (state === undefined) {
  3.     return 0
  4.   }
  5.   switch (action.type) {
  6.     case "increment": {
  7.       let newState = state
  8.       newState += 1
  9.       return newState
  10.     }
  11.     case "decrement":{
  12.       let newState = state
  13.       newState -= 1
  14.       return newState
  15.     }
  16.     default:
  17.       return state
  18.   }
  19. }
  20. export default counterReducer
复制代码
应用示例:
  1. import React, { useEffect, useState } from 'react'
  2. import Store from './1-AppStore/store'
  3. import { increment, decrement } from './1-AppStore/actions'
  4. export default function ReduxBaseJs() {
  5.   const [count, setcount] = useState(Store.getState())
  6.   useEffect(()=>{
  7.     Store.subscribe(() => {
  8.       console.log(Store.getState())
  9.       setcount(Store.getState())
  10.     })
  11.   })
  12.   
  13.   return (
  14.    
  15.       
  16.         <button
  17.           aria-label="Increment value"
  18.           onClick={() => Store.dispatch(increment())}
  19.         >
  20.           Increment
  21.         </button>
  22.         {count}
  23.         <button
  24.           aria-label="Decrement value"
  25.           onClick={() => Store.dispatch(decrement())}
  26.         >
  27.           Decrement
  28.         </button>
  29.       
  30.    
  31.   )
  32. }
复制代码
3. Redux 使用三大原则


  • state 以单一对象存储在 store 对象中;
  • state 只读,每次更新都是返回一个新的对象;
  • 使用纯函数 reducer 执行 state 更新;

    • 对外界没有副作用。即调用后对外界变量、对象没有影响。
    • 同样的输入得到同样的输出。

4. 你需要使用 Redux 吗?

虽然 Redux 是一个很有价值的管理状态工具,但还是要考虑下它是否适合你的场景。不要仅仅因为有人说过应该使用 Redux 而使用 - 应该花一些时间来了解使用它的潜在好处和取舍。当遇到如下问题时,建议开始使用 Redux:

  • 你有很多数据随时间而变化;
  • 你希望状态有一个唯一确定的来源(single source of truth);
  • 你发现将所有状态放在顶层组件中管理已不可维护;
5. Redux 使用之 Reducer 函数拆分 combineReducers

应用的整体全局状态以对象树的方式存放于单个 store。 唯一改变状态树(state tree)的方法是创建 action,一个描述发生了什么的对象,并将其 dispatch 给 store。 要指定状态树如何响应 action 来进行更新,你可以编写纯 reducer 函数,这些函数根据旧 state 和 action 来计算新 state。
Redux 扩展(行为拆分),如果不同的 Action 所处理的属性之间没有联系,我们可以把 Reducer 函数拆分。不同的函数负责处理不同属性,最終把它们合并成一个大的 Reducer 即可。那在 dispatch(action) 的时候是怎么知道用那个 reducer 来处理的呢?所有的 reducer 都会执行一遍,其实就是所有的 reducer 轮询匹配。
store.js 文件:
  1. // 创建合并 Reducer
  2. import {combineReducers} from "redux";
  3. const reducer = combineReducers ({
  4.   aReducer,
  5.   bReducer,
  6.   cReducer
  7. })
  8. const store = createStore(reducer)
复制代码
访问:
  1. // 访问:
  2. store.getState.aReducer.property // 不同的命名空间
复制代码
TS 示例:
reducer.ts 文件:
  1. // 定义一个 Reducer 纯函数
  2. const counterReducer = (state: any, action: any) => {
  3.   if (state === undefined) {
  4.     return 0
  5.   }
  6.   switch (action.type) {
  7.     case "increment": {
  8.       let newState = state
  9.       newState += 1
  10.       return newState
  11.     }
  12.     case "decrement":{
  13.       let newState = state
  14.       newState -= 1
  15.       return newState
  16.     }
  17.   
  18.     default:
  19.       return state
  20.   }
  21. }
  22. export default counterReducer
复制代码
actions.ts 文件:
  1. // 定义相关行为 actionvar increment = () => {
  2.   return {
  3.     type: "increment"
  4.   }
  5. }
  6. var decrement = () => {
  7.   return {
  8.     type: "decrement"
  9.   }
  10. }
  11. export { increment, decrement }
复制代码
store.ts 文件:
  1. import { createStore } from "@reduxjs/toolkit";
  2. import counterReducer from "./reducer";
  3. const Store = createStore(counterReducer)
  4. export default Store
复制代码
component.ts 文件:
  1. import React, { useEffect, useState } from 'react'
  2. import Store from './2-AppStore/store'
  3. import { increment, decrement } from './2-AppStore/actions'
  4. export default function ReduxBaseTs() {
  5.   const [count, setcount] = useState(Store.getState())
  6.   useEffect(() => {
  7.     Store.subscribe(() => {
  8.       console.log(Store.getState())
  9.       setcount(Store.getState())
  10.     })
  11.   })
  12.   return (
  13.    
  14.       
  15.         <button
  16.           aria-label="Increment value"
  17.           onClick={() => Store.dispatch(increment())}
  18.         >
  19.           Increment
  20.         </button>
  21.         {count}
  22.         <button
  23.           aria-label="Decrement value"
  24.           onClick={() => Store.dispatch(decrement())}
  25.         >
  26.           Decrement
  27.         </button>
  28.       
  29.    
  30.   )
  31. }
复制代码
⚠️:以上基础使用方法已经不是官方推荐的编写方式了,现在推荐使用 Redux Toolkit 编写 Redux 逻辑的方法。
6. React-redux

其实 Redux 和 React 没有任何关系,它是基于 Flux 实现的一套可用于 React 状态管理的库。而 React-redux 是基于 Redux 库(必须引入、依赖 Redux),在 Redux 基础上多了一点 React 特性。帮你构建父组件以及订阅和发布这样的一些事情。这样在 React 中状态管理使用更加方便。
React-redux 在 Redux 基础上,通过  connect 高阶函数生成高阶组件(父组件)包装订阅、发布功能(帮你订阅和取消订阅),不用开发者自己发起订阅和发布。其次通过最外层 Provider 供应商组件负责把 store 跨级给 connect 组件,原理就是通过 context 一级一级将 store 传递给 connect 组件。即通过 connect 包装就将 App UI 组件变成了容器组件,之前的组件就变成了 UI 组件。具体应用参考 Redux 中文官网。
https://github.com/reduxjs/react-redux
Redux Toolkit 一般在 React 项目中结合 React-redux 使用。
》UI 组件与容器组件:
1)UI 组件

  • 只负责 UI 的呈现,不带有任何业务逻辑;
  • 没有状态(即不使用 this.state 这个变量),所有数据都由参数(this.props)提供;
  • 不使用任何 Redux 的 API;
2)容器组件

  • 负责管理数据和业务逻辑,不负责 UI 的呈现;
  • 带有内部状态;
  • 使用 Redux 的 API;
》高阶组件(HOC:Higher order components) 与 context 通信在 react-redux 底层中的应用:

  • connect 是 HOC,高阶组件;
  • Provider 组件,可以让容器组件拿到 state,使用了context;
高阶组件构建与应用:HOC 不仅仅是一个方法,确切说应该是一个组件工厂,获取低阶组件,生成高阶组件。

  • 代码复用,代码模块化;
  • 增删改 props;
  • 渲染劫持;
7. Redux ToolKit

Redux Toolkit 简化了编写 Redux 逻辑和设置 store 的过程。 使用 Redux Toolkit,相同的示例逻辑如下所示。更详细使用可以参考 Demo 工程和 Redux 中文官网。
相关 API:

  • createSlice;
  • configureStore;
TS 示例:
  1. import { createSlice, configureStore } from '@reduxjs/toolkit'
  2. const counterSlice = createSlice({
  3.   name: 'counter',
  4.   initialState: {
  5.     value: 0
  6.   },
  7.   reducers: {
  8.     incremented: state => {
  9.       // Redux Toolkit 允许在 reducers 中编写 "mutating" 逻辑。
  10.       // 它实际上并没有改变 state,因为使用的是 Immer 库,检测到“草稿 state”的变化并产生一个全新的
  11.       // 基于这些更改的不可变的 state。
  12.       state.value += 1
  13.     },
  14.     decremented: state => {
  15.       state.value -= 1
  16.     }
  17.   }
  18. })
  19. export const { incremented, decremented } = counterSlice.actions
  20. const store = configureStore({
  21.   reducer: counterSlice.reducer
  22. })
  23. // 可以订阅 store
  24. store.subscribe(() => console.log(store.getState()))
  25. // 将我们所创建的 action 对象传递给 `dispatch`
  26. store.dispatch(incremented())
  27. // {value: 1}
  28. store.dispatch(incremented())
  29. // {value: 2}
  30. store.dispatch(decremented())
  31. // {value: 1}
复制代码
8. Redux 中间件

在 redux 中,action 仅仅是携带了数据的普通 js 对象。action creator 返回的值是这个 action 类型的对象。然后通过 store.dispatch() 进行分发。同步情况下一切都很完美,但是 reducer 无法处理异步的情况。那么我们就需要在 action 和 reducer 中间架起一座桥梁来处理异步。这就是 middleware。
中间件的由来与原理、机制:
  1. export default function thunkMiddleware({ dispatch, getstate }) {
  2.   return next => action =>
  3.     typeof action === 'function'?
  4.       action (dispatch, getstate):
  5.       next (action);
  6. }
复制代码
这段代码的意思是,中间件这个桥梁接受到的参数 action,如果不是 function 则和过去一样直接执行 next 方法(下一步处理),相当于中间件没有做任何事。如果 action 是 function,则先执行 action,action 的处理结束之后,再在 action 的内部调用 dispatch。
8.1 redux-thunk

到目前为止,我们学习到所有逻辑都是同步的。我们需要一个地方在我们的 Redux 应用程序中放置异步逻辑。这就需要使用中间件 redux-thunk。
thunk 是一种特定类型的 Redux 函数,可以包含异步逻辑。Thunk 是使用两个函数编写的:

  • 一个内部 thunk 函数,它以 dispatch 和 getState 作为参数;
  • 外部创建者函数,它创建并返回 thunk 函数;
从 counterSlice 导出的函数就是一个 thunk action creator 的例子。
  1. // 下面这个函数就是一个 thunk ,它使我们可以执行异步逻辑
  2. // 你可以 dispatched 异步 action `dispatch(incrementAsync(10))` 就像一个常规的 action
  3. // 调用 thunk 时接受 `dispatch` 函数作为第一个参数
  4. // 当异步代码执行完毕时,可以 dispatched actions
  5. export const incrementAsync = amount => dispatch => {
  6.   setTimeout(() => {
  7.     dispatch(incrementByAmount(amount))
  8.   }, 1000)
  9. }
复制代码
我们可以像使用普通 Redux action creator 一样使用它们:
  1. store.dispatch(incrementAsync(5))
复制代码
8.2 redux-promise

Redux 异步逻辑另外一种解决方案 redux-promise 中间件。用 promise 对象代替 Redux-thunk 中的函数。
8.3 redux-saga

redux-saga 相比 redux-thunk、redux-promise 能够非侵入式结合 redux 进行开发。让你的 action 还是之前那个普通的 action 对象,然后你需要引入我 saga 中的一些任务、effect 作用等等来处理。
8.3.1 生成器函数 Generator

Generator 生成器函数,ES6 中提供异步编程的一种解决方案。有时候也被被人称为状态机,可以让函数中断执行,等你需要推一步就走一步,可以生成输出多个状态,所以又叫状态机。
生成器函数特征:

  • 函数名前面增加 * 号。
  • 必须使用关键字 yield(产出状态值)。
  • yield 后面跟的是状态机生成的状态。即当遇到 yield 表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的 value 属性值。yield 表达式本身没有返回值,或者说总是返回undefined。
  • next 方法可以带一个参数,该参数就会被当作上一个 yield 表达式的返回值。
  1. function *test() {
  2.   console.log("111111")
  3.   yield;
  4.   console.log("222222")
  5.   yield;
  6.   console.log("333333")
  7.   yield;
  8. }
  9. let generator = test()
  10. // next() 执行器函数执行一次,直到遇到 yield 关键字
  11. generator.next() // 111111
  12. generator.next() // 222222
  13. generator.next() // 333333
  14. generator.next() // 没有任何输出了,已经结束了
  15. function *test1() {
  16.   console.log("111111")
  17.   let value1 = yield "yield return 1 step";
  18.   console.log("222222", value1)
  19.   let value2 = yield "yield return 2 step";
  20.   console.log("333333", value2)
  21.   let value3 = yield "yield return 3 step";
  22.   console.log("333333", value3)
  23. }
  24. let generator1 = test1()
  25. let gen1 = generator1.next("1 next 参数")
  26. console.log(gen1) // {value: 'yield return 1 step', done: false}
  27. let gen2 = generator1.next("2 next 参数")
  28. console.log(gen2) // {value: 'yield return 2 step', done: false}
  29. let gen3 =generator1.next("3 next 参数")
  30. console.log(gen3) // {value: 'yield return 3 step', done: false}
  31. let gen4 =generator1.next("4 next 参数")
  32. console.log(gen4) // {value: undefined, done: true}
复制代码
异步链式调用更简单的写法 async-await 写法,async-await 本质是生成器的一套语法糖,内置了执行器函数。让异步变写得和同步的一样简单。但是这里 redux-saga 是基于生成器函数来实现的,我们了解即可:
  1. async function test() {
  2.   var res1 = await fetch();
  3.   var res2 = await fetch(res1);
  4.   var res3 = await fetch(res2);
  5. }
复制代码
8.3.2 redux-saga 应用

在 saga 中,全局监听器和接收器使用 Generator 函数和 saga 自身的一些辅助函数实现对整个流程的管控。
3.png
  1. // Component 组件内部
  2. dispatch({action:"get-list"})
  3. // WatcherSaga
  4. // saga.js 文件
  5. function *watchSaga() {
  6.   while(true) {
  7.     // take 监听 组件发来的 action
  8.     yield take("get-list")
  9.     // fork 同步非阻塞执行函数 getList
  10.     yield fork(getList)
  11.   }
  12. }
  13. function *getList() {
  14.   // 异步处理:call 函数发布异步请求 - 阻塞式调用
  15.   let res = yield call(getListAction) //这里传入返回值是promise对象的函数
  16.   // put 函数发出新的 action
  17.   yield put({
  18.     type: "change-list",
  19.     payload: res
  20.   })
  21. }
  22. function getListAction() {
  23.   return new Promise((resolve, reject)=>{
  24.     setTime(()={
  25.       resolve(["111","222","333"])
  26.     },2000)
  27.   })
  28. }
  29.                      
  30. export default watchSage
复制代码
  1. // store.js 文件
  2. import {createStore, applyMiddleware} from 'redux'
  3. import reducer from ' /reducer'
  4. import createSagaMidlleWare from 'redux-saga'
  5. import watchSaga from •/ saga'
  6. const SagaMidlleWare = createSagaMidlleWare()
  7. const store = createStore(reducer, applyMiddleware(SagaMidlleWare))
  8. SagaMidlleWare.run(latchSaga) //saga 任务,
  9. export default store
复制代码
多任务同时监听 all:
  1. // WatcherSaga
  2. // saga2.js 文件                  
  3. export default watchSage
  4. // 聚合统一监听多个任务 saga.js 文件
  5. import {all} from 'redux-saga/effects'
  6. import watchSagal from '•/saga/sagal'
  7. import watchSaga2 from './saga/saga2'
  8. function *watchSaga(){
  9.         yield all([watchSaga1(),watchSaga2()])
  10. }
  11. export default watchSaga
复制代码
多异步链式流程调用:
  1. function *getList() {
  2.   // 异步处理:call 函数发布异步请求 - 阻塞式调用
  3.   let res = yield call(getListAction) //这里传入返回值是promise对象的函数
  4.   let res1 = yield call(getListAction1, res)
  5.   // put 函数发出新的 action
  6.   yield put({
  7.     type: "change-list",
  8.     payload: res1
  9.   })
  10. }
  11. function getListAction() {
  12.   return new Promise((resolve, reject)=>{
  13.     setTime(()={
  14.       resolve(["111","222","333"])
  15.     },2000)
  16.   })
  17. }
  18.                      
  19. function getListAction1(data) {
  20.   return new Promise((resolve, reject)=>{
  21.     setTime(()={
  22.       resolve([...data, "444"])
  23.     },2000)
  24.   })
  25. }
  26.                      
  27. export default watchSage
复制代码
watchSaga 函数新写法-合并 take 和 fork:
  1. // WatcherSaga
  2. // saga.js 文件
  3. function *watchSaga() {
  4.   /*while(true) {
  5.     // take 监听 组件发来的 action
  6.     yield take("get-list")
  7.     // fork 同步非阻塞执行函数 getList
  8.     yield fork(getList)
  9.   }*/
  10.   yield takeEvery("get-list", getList)
  11. }
复制代码
8.3.3 redux-saga 应用场景

在 React-Redux 应用中,‌redux-saga 主要用于管理复杂异步逻辑和副作用‌,尤其在以下场景中具有显著优势:
多步骤异步操作:‌当操作涉及多个顺序/并行的异步任务(如:登录 → 获取用户信息 → 加载权限列表),saga 的 Generator 函数可用 yield 精确控制每一步流程,避免回调地狱‌
  1. function* loginFlow() {
  2.   yield call(loginAPI);        // 步骤1:登录
  3.   yield call(fetchUserInfo);   // 步骤2:获取用户信息
  4.   yield call(loadPermissions); // 步骤3:加载权限
  5. }
复制代码
依赖异步结果的后续操作:若后续操作需依赖多个异步任务结果(如:支付需同时验证账户余额和风控状态),saga 可通过 all 实现并行请求,并统一处理结果‌。
优选 saga 的场景:‌多步骤异步‌、‌高可测性要求‌、‌长时运行任务‌(如实时通信)‌。
简单场景(单一请求)可使用 redux-thunk 或 Redux Toolkit 内置方案‌。
9. Redux 插件

9.1 redux-persist

redux-persist 是一个用于 Redux 状态管理的持久化插件,允许将应用状态保存到本地存储(如 localStorage),以便在应用重启或页面刷新时恢复状态。必须配合 React-redux 使用。

来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除

相关推荐

您需要登录后才可以回帖 登录 | 立即注册