当你越来越有能力时,自然会有人看得起你;改变自己,你才有自信,梦想才会慢慢的实现。喷泉之所以漂亮是因为她有了压力;瀑布之所以壮观是因为她没有了退路;水之所以能穿石是因为永远在坚持。

首先我们要明确一个 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 事件后,更新页面;

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 文件:

var increment = () => {
return {
type: "increment"
}
} var decrement = () => {
return {
type: "decrement"
}
} export { increment, decrement }

Store 文件:

import { createStore } from "@reduxjs/toolkit";
import counterReducer from "./reducer"; const Store = createStore(counterReducer) export default Store

Reducer 文件:

const counterReducer = (state, action) => {
if (state === undefined) {
return 0
}
switch (action.type) {
case "increment": {
let newState = state
newState += 1
return newState
}
case "decrement":{
let newState = state
newState -= 1
return newState
}
default:
return state
}
} export default counterReducer

应用示例:

import React, { useEffect, useState } from 'react'
import Store from './1-AppStore/store'
import { increment, decrement } from './1-AppStore/actions' export default function ReduxBaseJs() {
const [count, setcount] = useState(Store.getState())
useEffect(()=>{
Store.subscribe(() => {
console.log(Store.getState())
setcount(Store.getState())
})
}) return (
<div>
<div>
<button
aria-label="Increment value"
onClick={() => Store.dispatch(increment())}
>
Increment
</button>
<span>{count}</span>
<button
aria-label="Decrement value"
onClick={() => Store.dispatch(decrement())}
>
Decrement
</button>
</div>
</div>
)
}

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 文件:

// 创建合并 Reducer
import {combineReducers} from "redux";
const reducer = combineReducers ({
aReducer,
bReducer,
cReducer
})
const store = createStore(reducer)

访问:

// 访问:
store.getState.aReducer.property // 不同的命名空间

TS 示例:

reducer.ts 文件:

// 定义一个 Reducer 纯函数
const counterReducer = (state: any, action: any) => {
if (state === undefined) {
return 0
}
switch (action.type) {
case "increment": {
let newState = state
newState += 1
return newState
}
case "decrement":{
let newState = state
newState -= 1
return newState
} default:
return state
}
}
export default counterReducer

actions.ts 文件:

// 定义相关行为 action
var increment = () => {
return {
type: "increment"
}
}
var decrement = () => {
return {
type: "decrement"
}
}
export { increment, decrement }

store.ts 文件:

import { createStore } from "@reduxjs/toolkit";
import counterReducer from "./reducer"; const Store = createStore(counterReducer) export default Store

component.ts 文件:

import React, { useEffect, useState } from 'react'
import Store from './2-AppStore/store'
import { increment, decrement } from './2-AppStore/actions' export default function ReduxBaseTs() {
const [count, setcount] = useState(Store.getState())
useEffect(() => {
Store.subscribe(() => {
console.log(Store.getState())
setcount(Store.getState())
})
}) return (
<div>
<div>
<button
aria-label="Increment value"
onClick={() => Store.dispatch(increment())}
>
Increment
</button>
<span>{count}</span>
<button
aria-label="Decrement value"
onClick={() => Store.dispatch(decrement())}
>
Decrement
</button>
</div>
</div>
)
}

️:以上基础使用方法已经不是官方推荐的编写方式了,现在推荐使用 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 示例:

import { createSlice, configureStore } from '@reduxjs/toolkit'

const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
incremented: state => {
// Redux Toolkit 允许在 reducers 中编写 "mutating" 逻辑。
// 它实际上并没有改变 state,因为使用的是 Immer 库,检测到“草稿 state”的变化并产生一个全新的
// 基于这些更改的不可变的 state。
state.value += 1
},
decremented: state => {
state.value -= 1
}
}
}) export const { incremented, decremented } = counterSlice.actions const store = configureStore({
reducer: counterSlice.reducer
}) // 可以订阅 store
store.subscribe(() => console.log(store.getState())) // 将我们所创建的 action 对象传递给 `dispatch`
store.dispatch(incremented())
// {value: 1}
store.dispatch(incremented())
// {value: 2}
store.dispatch(decremented())
// {value: 1}

8. Redux 中间件

在 redux 中,action 仅仅是携带了数据的普通 js 对象。action creator 返回的值是这个 action 类型的对象。然后通过 store.dispatch() 进行分发。同步情况下一切都很完美,但是 reducer 无法处理异步的情况。那么我们就需要在 action 和 reducer 中间架起一座桥梁来处理异步。这就是 middleware。

中间件的由来与原理、机制:

export default function thunkMiddleware({ dispatch, getstate }) {
return next => action =>
typeof action === 'function'?
action (dispatch, getstate):
next (action);
}

这段代码的意思是,中间件这个桥梁接受到的参数 action,如果不是 function 则和过去一样直接执行 next 方法(下一步处理),相当于中间件没有做任何事。如果 action 是 function,则先执行 action,action 的处理结束之后,再在 action 的内部调用 dispatch。

8.1 redux-thunk

到目前为止,我们学习到所有逻辑都是同步的。我们需要一个地方在我们的 Redux 应用程序中放置异步逻辑。这就需要使用中间件 redux-thunk。

thunk 是一种特定类型的 Redux 函数,可以包含异步逻辑。Thunk 是使用两个函数编写的:

  • 一个内部 thunk 函数,它以 dispatchgetState 作为参数;
  • 外部创建者函数,它创建并返回 thunk 函数;

counterSlice 导出的函数就是一个 thunk action creator 的例子。

// 下面这个函数就是一个 thunk ,它使我们可以执行异步逻辑
// 你可以 dispatched 异步 action `dispatch(incrementAsync(10))` 就像一个常规的 action
// 调用 thunk 时接受 `dispatch` 函数作为第一个参数
// 当异步代码执行完毕时,可以 dispatched actions
export const incrementAsync = amount => dispatch => {
setTimeout(() => {
dispatch(incrementByAmount(amount))
}, 1000)
}

我们可以像使用普通 Redux action creator 一样使用它们:

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 中提供异步编程的一种解决方案。有时候也被被人称为状态机,可以让函数中断执行,等你需要推一步就走一步,可以生成输出多个状态,所以又叫状态机。

生成器函数特征:

  1. 函数名前面增加 * 号。
  2. 必须使用关键字 yield(产出状态值)。
  3. yield 后面跟的是状态机生成的状态。即当遇到 yield 表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的 value 属性值。yield 表达式本身没有返回值,或者说总是返回undefined。
  4. next 方法可以带一个参数,该参数就会被当作上一个 yield 表达式的返回值。
function *test() {
console.log("111111")
yield;
console.log("222222")
yield;
console.log("333333")
yield;
} let generator = test()
// next() 执行器函数执行一次,直到遇到 yield 关键字
generator.next() // 111111
generator.next() // 222222
generator.next() // 333333
generator.next() // 没有任何输出了,已经结束了 function *test1() {
console.log("111111")
let value1 = yield "yield return 1 step";
console.log("222222", value1)
let value2 = yield "yield return 2 step";
console.log("333333", value2)
let value3 = yield "yield return 3 step";
console.log("333333", value3)
} let generator1 = test1() let gen1 = generator1.next("1 next 参数")
console.log(gen1) // {value: 'yield return 1 step', done: false}
let gen2 = generator1.next("2 next 参数")
console.log(gen2) // {value: 'yield return 2 step', done: false}
let gen3 =generator1.next("3 next 参数")
console.log(gen3) // {value: 'yield return 3 step', done: false} let gen4 =generator1.next("4 next 参数")
console.log(gen4) // {value: undefined, done: true}

异步链式调用更简单的写法 async-await 写法,async-await 本质是生成器的一套语法糖,内置了执行器函数。让异步变写得和同步的一样简单。但是这里 redux-saga 是基于生成器函数来实现的,我们了解即可:

async function test() {
var res1 = await fetch();
var res2 = await fetch(res1);
var res3 = await fetch(res2);
}
8.3.2 redux-saga 应用

在 saga 中,全局监听器和接收器使用 Generator 函数和 saga 自身的一些辅助函数实现对整个流程的管控。

// Component 组件内部
dispatch({action:"get-list"}) // WatcherSaga
// saga.js 文件
function *watchSaga() {
while(true) {
// take 监听 组件发来的 action
yield take("get-list")
// fork 同步非阻塞执行函数 getList
yield fork(getList)
}
} function *getList() {
// 异步处理:call 函数发布异步请求 - 阻塞式调用
let res = yield call(getListAction) //这里传入返回值是promise对象的函数
// put 函数发出新的 action
yield put({
type: "change-list",
payload: res
})
} function getListAction() {
return new Promise((resolve, reject)=>{
setTime(()={
resolve(["111","222","333"])
},2000)
})
} export default watchSage
// store.js 文件

import {createStore, applyMiddleware} from 'redux'
import reducer from ' /reducer'
import createSagaMidlleWare from 'redux-saga'
import watchSaga from •/ saga' const SagaMidlleWare = createSagaMidlleWare()
const store = createStore(reducer, applyMiddleware(SagaMidlleWare)) SagaMidlleWare.run(latchSaga) //saga 任务,
export default store

多任务同时监听 all:

// WatcherSaga
// saga2.js 文件
export default watchSage // 聚合统一监听多个任务 saga.js 文件
import {all} from 'redux-saga/effects'
import watchSagal from '•/saga/sagal'
import watchSaga2 from './saga/saga2'
function *watchSaga(){
yield all([watchSaga1(),watchSaga2()])
}
export default watchSaga

多异步链式流程调用:

function *getList() {
// 异步处理:call 函数发布异步请求 - 阻塞式调用
let res = yield call(getListAction) //这里传入返回值是promise对象的函数
let res1 = yield call(getListAction1, res)
// put 函数发出新的 action
yield put({
type: "change-list",
payload: res1
})
} function getListAction() {
return new Promise((resolve, reject)=>{
setTime(()={
resolve(["111","222","333"])
},2000)
})
} function getListAction1(data) {
return new Promise((resolve, reject)=>{
setTime(()={
resolve([...data, "444"])
},2000)
})
} export default watchSage

watchSaga 函数新写法-合并 take 和 fork:

// WatcherSaga
// saga.js 文件
function *watchSaga() {
/*while(true) {
// take 监听 组件发来的 action
yield take("get-list")
// fork 同步非阻塞执行函数 getList
yield fork(getList)
}*/
yield takeEvery("get-list", getList)
}
8.3.3 redux-saga 应用场景

在 React-Redux 应用中,‌redux-saga 主要用于管理复杂异步逻辑和副作用‌,尤其在以下场景中具有显著优势:

多步骤异步操作:‌当操作涉及多个顺序/并行的异步任务(如:登录 → 获取用户信息 → 加载权限列表),sagaGenerator 函数可用 yield 精确控制每一步流程,避免回调地狱‌

function* loginFlow() {
yield call(loginAPI); // 步骤1:登录
yield call(fetchUserInfo); // 步骤2:获取用户信息
yield call(loadPermissions); // 步骤3:加载权限
}

依赖异步结果的后续操作:若后续操作需依赖多个异步任务结果(如:支付需同时验证账户余额和风控状态),saga 可通过 all 实现并行请求,并统一处理结果‌。

优选 saga 的场景:‌多步骤异步‌、‌高可测性要求‌、‌长时运行任务‌(如实时通信)‌。

简单场景(单一请求)可使用 redux-thunk 或 Redux Toolkit 内置方案‌。

9. Redux 插件

9.1 redux-persist

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

Redux 状态容器、管理的更多相关文章

  1. 微信小程序里使用 Redux 状态管理

    微信小程序里使用 Redux 状态管理 前言 前阵子一直在做小程序开发,采用的是官方给的框架 wepy , 如果还不了解的同学可以去他的官网查阅相关资料学习:不得不说的是,这个框架确相比于传统小程序开 ...

  2. redux状态管理和react-redux的结合使用

    一:调试 注意:Redux调试工具.谷歌中搜redux同理react 新建store的时候判断window.devToolsExtension使用compose(组合函数)结合thunk插件和wind ...

  3. Docker背后的容器管理——Libcontainer深度解析

    Libcontainer 是Docker中用于容器管理的包,它基于Go语言实现,通过管理namespaces.cgroups.capabilities以及文件系统来进行容器控制.你可以使用Libcon ...

  4. Docker系列三:Docker容器管理

    Docker容器管理 1. 单一容器管理 1) 容器的启动 $ docker run --name gitlab-redis -d --volume /srv/docker/gitlab/redis: ...

  5. Docker 容器管理

    单一容器管理 容器的标识符 每个容器被创建后都会分配一个CONTAINER_ID作为容器的唯一标识符,后续的启动.停止等操作都通过CONTAINER_ID来完成的. CONTAINER_ID很难记忆, ...

  6. Rancher 容器管理平台-免费视频培训-链接及内容-第三季

    Rancher 容器管理平台-免费视频培训-链接及内容 第三季 第5期-2018年05月10日-持续集成的容器化实践回放网址:http://www.itdks.com/liveevent/detail ...

  7. 5.容器管理【Docker每天5分钟】

    原文:5.容器管理[Docker每天5分钟] Docker给PaaS世界带来的“降维打击”,其实是提供了一种非常便利的打包机制.该机制打包了应用运行所需要的整个操作系统,从而保证了本地环境和云端环境的 ...

  8. Docker笔记(六):容器管理

    原文地址:http://blog.jboost.cn/2019/07/21/docker-6.html 容器是Docker中的另一核心概念,在Docker中,应用的运行都是在容器内进行的,容器则基于镜 ...

  9. 基于kubernetes自研容器管理平台的技术实践

    一.容器云的背景 伴随着微服务的架构的普及,结合开源的Dubbo和Spring Cloud等微服务框架,宜信内部很多业务线逐渐了从原来的单体架构逐渐转移到微服务架构.应用从有状态到无状态,具体来说将业 ...

  10. 企业级容器管理平台 Rancher 介绍入门及如何备份数据

    企业级容器管理平台 Rancher 介绍入门及如何备份数据 是什么 Rancher 是一个为 DevOps 团队提供的完整的 Kubernetes 与容器管理解决方案的开源的企业级容器管理平台.它解决 ...

随机推荐

  1. Sqlite3中的Join

    1.概述sqlite3是一种轻便的数据库,由DDL(Data defination language),DML(Data manipulation language),TCL(Transaction ...

  2. codeup之字符串比较

    Description 比较两个字符串s1和s2的大小,如果s1>s2,则输出一个正数:若s1=s2,则输出0:若s1<s2,则输出一个负数. 要求:不用strcpy函数:两个字符串用ge ...

  3. JavaScript入门笔记day1

    文章目录 啥是JavaScript JavaScript与HTML的结合方式 js文件在HTML中的位置 注释方式 变量 命名规范: 定义的关键字 To be a struggling Rick fo ...

  4. 抽象类&&接口做形参(其实同理)

    抽象类:传入该抽象类的子类对象 eg: package javaBasic; public class TestAbstract { public static void main(String[] ...

  5. AI工具 Cursor + MCP服务:提效与落地的完美组合

    在当今软件开发快速演进的时代,开发者们越来越依赖于人工智能辅助工具来提升效率与质量.最近我在实际项目中深度使用了 Cursor 编辑器 和 MCP(Model Context Protocol)服务, ...

  6. 开源ERP系统 Odoo 18 介绍

    开源ERP系统 Odoo 18 介绍 1. ERP 简介 企业资源计划(ERP,Enterprise Resource Planning)是一种集成软件系统,旨在帮助企业管理和优化业务流程.ERP 系 ...

  7. RBMQ中python案例一:简单模式

    一.生产者与消费者模式之 简单模式,原理图 二.生产者产生消息 import json import pika import datetime # 生产者 producer.py def get_me ...

  8. 2024牛客多校2B MST

    同步发布于我的网站. Problem Sajin最近深入研究了最小生成树,现在他已经掌握了MST的算法.他渴望通过一系列查询来评估您对最小生成树概念的掌握程度. 您将面临一个加权无向图,该图包含没有任 ...

  9. 【语义分割专栏】2:U-net原理篇(由浅入深)

    目录 前言 背景介绍 U-net核心剖析 编码解码结构(U形状) 卷积模式 跳跃连接 add concat 其他细节 overlap-tile策略 弹性形变 U-net模型代码 结语 参考资料 前言 ...

  10. git基础及gitee配置

    安装git 网址:https://git-scm.com/book/zh/v2/起步-安装-Git 使用git 基本指令 # 初始化指令 git init # 管理目录下的文件状态 注:新增文件和修改 ...