当你越来越有能力时,自然会有人看得起你;改变自己,你才有自信,梦想才会慢慢的实现。喷泉之所以漂亮是因为她有了压力;瀑布之所以壮观是因为她没有了退路;水之所以能穿石是因为永远在坚持。
首先我们要明确一个 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 (
-
-
- <button
- aria-label="Increment value"
- onClick={() => Store.dispatch(increment())}
- >
- Increment
- </button>
- {count}
- <button
- aria-label="Decrement value"
- onClick={() => Store.dispatch(decrement())}
- >
- Decrement
- </button>
-
-
- )
- }
复制代码 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 文件:- // 定义相关行为 actionvar 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 (
-
-
- <button
- aria-label="Increment value"
- onClick={() => Store.dispatch(increment())}
- >
- Increment
- </button>
- {count}
- <button
- aria-label="Decrement value"
- onClick={() => Store.dispatch(decrement())}
- >
- Decrement
- </button>
-
-
- )
- }
复制代码 ⚠️:以上基础使用方法已经不是官方推荐的编写方式了,现在推荐使用 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 函数,它以 dispatch 和 getState 作为参数;
- 外部创建者函数,它创建并返回 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 中提供异步编程的一种解决方案。有时候也被被人称为状态机,可以让函数中断执行,等你需要推一步就走一步,可以生成输出多个状态,所以又叫状态机。
生成器函数特征:
- 函数名前面增加 * 号。
- 必须使用关键字 yield(产出状态值)。
- yield 后面跟的是状态机生成的状态。即当遇到 yield 表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的 value 属性值。yield 表达式本身没有返回值,或者说总是返回undefined。
- 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 主要用于管理复杂异步逻辑和副作用,尤其在以下场景中具有显著优势:
多步骤异步操作:当操作涉及多个顺序/并行的异步任务(如:登录 → 获取用户信息 → 加载权限列表),saga 的 Generator 函数可用 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 使用。
来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除 |