当你越来越有能力时,自然会有人看得起你;改变自己,你才有自信,梦想才会慢慢的实现。喷泉之所以漂亮是因为她有了压力;瀑布之所以壮观是因为她没有了退路;水之所以能穿石是因为永远在坚持。
首先我们要明确一个 React 组件,它与数据挂钩的只有 props 和 state,一个是从上级传下来的数据,一个是内部的状态,只能向下传,不能直接向上传。
这样的话,我们如何处理同级别组件的通信呢?一个最直接的方式就是创建一个最顶层的 state,把数据当作 props 向下传。这个最顶层的存放 state 的地方,我们可以认为是 store。
1. Flux 和 Redux
Flux 是一种架构思想,专门解决软件的结构问题,它跟 MVC 架构是同一类东西,但是更加简单和清晰。Flux 存在至少多种实现,比如:Redux。
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 文件:
react
var increment = () => {
return {
type: "increment"
}
}
var decrement = () => {
return {
type: "decrement"
}
}
export { increment, decrement }
Store 文件:
react
import { createStore } from "@reduxjs/toolkit";
import counterReducer from "./reducer";
const Store = createStore(counterReducer)
export default Store
Reducer 文件:
react
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
应用示例:
react
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 文件:
react
// 创建合并 Reducer
import {combineReducers} from "redux";
const reducer = combineReducers ({
aReducer,
bReducer,
cReducer
})
const store = createStore(reducer)
访问:
react
// 访问:
store.getState.aReducer.property // 不同的命名空间
TS 示例:
reducer.ts 文件:
tsx
// 定义一个 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 文件:
tsx
// 定义相关行为 action
var increment = () => {
return {
type: "increment"
}
}
var decrement = () => {
return {
type: "decrement"
}
}
export { increment, decrement }
store.ts 文件:
tsx
import { createStore } from "@reduxjs/toolkit";
import counterReducer from "./reducer";
const Store = createStore(counterReducer)
export default Store
component.ts 文件:
tsx
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 中文官网。
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 示例:
tsx
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。
中间件的由来与原理、机制:
react
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 函数,它以
dispatch
和getState
作为参数; - 外部创建者函数,它创建并返回 thunk 函数;
从 counterSlice
导出的函数就是一个 thunk action creator 的例子。
js
// 下面这个函数就是一个 thunk ,它使我们可以执行异步逻辑
// 你可以 dispatched 异步 action `dispatch(incrementAsync(10))` 就像一个常规的 action
// 调用 thunk 时接受 `dispatch` 函数作为第一个参数
// 当异步代码执行完毕时,可以 dispatched actions
export const incrementAsync = amount => dispatch => {
setTimeout(() => {
dispatch(incrementByAmount(amount))
}, 1000)
}
我们可以像使用普通 Redux action creator 一样使用它们:
js
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 表达式的返回值。
react
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 是基于生成器函数来实现的,我们了解即可:
react
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 自身的一些辅助函数实现对整个流程的管控。
react
// 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
react
// 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:
react
// 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
多异步链式流程调用:
react
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:
react
// 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
主要用于管理复杂异步逻辑和副作用**,尤其在以下场景中具有显著优势:
多步骤异步操作: 当操作涉及多个顺序/并行的异步任务(如:登录 → 获取用户信息 → 加载权限列表),saga
的 Generator
函数可用 yield
精确控制每一步流程,避免回调地狱
react
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 使用。