前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >用 Redux 做状态管理,真的很简单🦆!

用 Redux 做状态管理,真的很简单🦆!

作者头像
小东同学
发布2022-07-29 12:41:58
3.4K0
发布2022-07-29 12:41:58
举报
文章被收录于专栏:前端进阶实战

最近在某项目中欲选一工具用作项目的全局状态管理,通过综合比较考虑,最终选择了 Redux。都说 Redux 难上手,今天通过 1 个案例, 3 个函数帮小伙伴们快速掌握并实践生产!

作为一名前端工程师,不少小伙伴对于 Redux 的概念、使用可能还是比较模糊,上手使用的心智负担也比较重!

但通过调研,目前 Redux 的生态可以说是非常丰富,这也使得将其引入作为项目的状态管理工具库变得 更加容易

本文通过实际案例反向释义 Redux 中的名词概念,同时借助 @reduxjs/toolkit 模块简化 Redux 的使用,希望通过今天的分享可以帮助大家打开心结,抱抱 Redux,提升工作效率,从此不加班!

一、Redux 基础

一开始就阐释概念名词,可能会增加大家上手的难度,因此该部分只对 Redux 做最基本的一个认识。

1.1 什么是 Redux ?

ReduxJavaScript 状态容器,提供 可预测可调试集中式 的状态管理。

1.2 特点

  • 可预测: 让你开发出 行为稳定可预测、可运行在不同环境 (客户端、服务端和原生程序)、且 易于测试 的应用。
  • 集中管理: 集中管理应用的状态和逻辑可以让你开发出强大的功能,如 撤销/重做状态持久化 等等。
  • 可调试: Redux DevTools 让你 轻松追踪 到 应用的状态在何时、何处以及如何改变。Redux 的架构会记下每一次改变,借助于 "时间旅行调试",你甚至可以把完整的错误报告发送给服务器。
  • 灵活: Redux 可与任何 UI 层框架搭配使用,它体小精干(只有 2kB,包括依赖),并且有 庞大的插件生态 来实现你的需求。

1.3 设计思想

Redux 既然是状态管理库,那么接下来掌握一下基本的数据流概念和原则

(1) 单一数据源

整个应用的 全局 state 被储存在一棵对象树(object tree)中,并且这个对象树只存在于唯一 Store(存储) 中

单一数据源使得同构应用开发变得容易,将状态在统一的 对象树 中维护管理也会更加容易!

(2) 单向数据流(one-way data flow)

Redux 单向数据流

  1. state 来描述应用程序在特定时间点的状况
  2. 基于 state 来渲染出 View
  3. 当发生某些事情时(例如用户单击按钮),state 会根据发生的事情进行更新,生成新的 state
  4. 基于新的 state 重新渲染 View

(3) 不可变性(Immutability)

对于状态(state)的描述一般都是一个大的 JavaScript 对象(Object Tree),例如:

代码语言:javascript
复制
const state = {
    isLoading: true,
    userInfo: {
        uid: 1,
        wechat: 'DYBOY2020',
        phone: 177****7777,
        history: [1,2,3,4,5]
    }
}

由于 JS 的动态性,使得对象是可以修改的,Redux 想要记录每一个状态,如果直接修改 state 中的引用类型属性,势必会导致 state 的变化不可追溯和预测。

因此 state 是只读的!唯一改变 state 的方法就是触发 actionaction 是一个用于描述已发生事件的普通对象。

Redux 期望所有状态更新都是使用不可变的方式,因此,每一次的 state 变更,不会修改原对象,而是修改前一个状态(state)的克隆对象,以此来保证不可变性和正确性,同时记录每一次变化的 state

(4) 纯函数更新 state

纯函数: 相同的输入,总是会得到相同的输出,并且在执行过程中没有任何副作用的函数。

为了保证数据的改变正确性,以及满足 state 不可变性的要求,因此引入了 纯函数 作为更新状态的唯一方式。

React Hooks 的状态管理就融合了 Redux 的设计思想,毕竟把 Redux 的作者 Dan Abramov 都直接挖过去了🐶!

二、案例实践

下面讲讲如何接入一个全新的项目中,以 create-react-app[1] 脚手架创建的项目为例子。

借助 @redux/toolkit,不再需要刻意关心如何组织编写 ReducerAction creatorAction Type 等内容,同时,默认就融合支持 异步 Thunks

再结合 React 16.x 中的 Hooks,使用 useSelector()useDispatch() 在任意组件中消费 Store

2.1 初始化项目

首先是借助 create-react-app 初始化一个 TS + React 环境的项目

代码语言:javascript
复制
npx create-react-app craapp --template typescript

2.2 安装 Redux 相关依赖

代码语言:javascript
复制
yarn add redux react-redux @reduxjs/toolkit
  • redux: 核心状态管理库
  • react-redux: 用于 React 框架的桥接层
  • @reduxjs/toolkit: 降低 Redux 使用难度的助手

2.3 全局 Store 的创建

所有的状态都放在了 Store 中,因此需要一个统一的地方来管理,以一个计数器为例,在 ./src/store 下的文件结构如下:

代码语言:javascript
复制
.
├── index.ts // store 实例,导出 state 和 dispatch 类型
└── reducers // 集合所有的 reducer
    ├── counter.ts // 用于计数器的 reducer、action、selector
    └── index.ts // 导出 rootReducers,用于整合所有的 reducer

(1) store/index.ts

代码语言:javascript
复制
import { configureStore } from "@reduxjs/toolkit";
import rootReducers from "./reducers"; // 引入 reducer 的集合

// 实例化 store,全局唯一
const store = configureStore({
  reducer: rootReducers,
});

// 导出 Store 中的状态(state)类型
export type RootState = ReturnType<typeof store.getState>;

// 导出更改状态的 Dispatch 方法类型
export type AppDispatch = typeof store.dispatch;

// 默认导出 store,用于全局的 Provieder 消费
export default store;

(2) store/reducers/index.ts

代码语言:javascript
复制
import {combineReducers} from '@reduxjs/toolkit'
import counterSlice from './counter' // 可以引入各种 reducer

const rootReducers = combineReducers({
  counter: counterSlice // 这里通过 MAP 形式,自定义不同 reducer 的“命名空间”
  // ... 可以在这里扩展添加任意的 reducer
})

// 默认导出,给 configureStore 消费
export default rootReducers

(3) store/reducers/counter.ts

接下来看看怎么便捷的创建一个 Reducer,以前使用 Redux 总是需要手动创建多个文件,reduceractionaction creator,但现在可以直接借助 @redux/toolkit 统一的放在一个文件中,结构化的去描述 Redux 中的 actionredcuer

代码语言:javascript
复制
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { AppDispatch, RootState } from ".."; // 在 store/index.ts 中声明的类型

// 借助 createSlice 创建 reducer、action
const CounterSlice = createSlice({
  name: "counter", // 生成 Action type 的前缀,例如:counter/increment
  initialState: {
    value: 0,
  },
  reducers: {
    increment: (state) => {
      state.value += 1; // 这里默认通过了 immer 处理,不会修改原 state
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.value += action.payload;
    },
    decrementByAmount: (state, action: PayloadAction<number>) => {
      state.value -= action.payload;
    },
  },
});

// Action Creator 用于执行返回描述如何更新 state 的 Action
export const { increment, decrement, incrementByAmount, decrementByAmount } =
  CounterSlice.actions;

// 异步 thunk,用于需要在更新数据前异步处理数据的情况
export const incrementAsync = (amount: number) => (dispatch: AppDispatch) => {
  setTimeout(() => {
    dispatch(incrementByAmount(amount));
  }, 1500);
};

// Selector,作为 useSelector 读取数据的函数参数
export const counterSelector = (state: RootState) => state.counter.value;

// Reducer,真正执行修改 state 的纯函数
export default CounterSlice.reducer;

如上的写法可以作为一种“模板”,毋须关心各种概念之间的组合,直接用就可以了!

代码语言:javascript
复制
console.log(CounterSlice)
/*
output:
{
  name: 'counter',
  actions : {
    increment,
    decrement,
    incrementByAmount,
    decrementByAmount
  },
  reducer
}
*/

上述 actions 中的函数就是 Action creator,例如执行 increment() 返回的就是:

代码语言:javascript
复制
{type: 'counter/increment'}

执行 incrementByAmount(5) 返回的是:

代码语言:javascript
复制
{type: 'counter/incrementByAmount', payload: 5}

2.4 组件中读写 Store

首先是需要将 Store 实例绑定到我们的应用上。

./src/index.tsx 中添加如下:

代码语言:javascript
复制
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux"; // 引入 Provider,绑定 store 到应用上
import store from "./store"; // 引入 store 实例
import App from "./App";
import "./index.css";


ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}> {/* 绑定 store */}
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById("root")
);

结合 react-redux 提供的 useSelector()useDispatch() 可以在我们自定义的 Counter 组件中消费 counter 状态(数据)

代码语言:javascript
复制
//文件位置:src/pages/counter/index.tsx
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import { decrement, incrementAsync, counterSelector } from "@/store/reducers/counter";
import "./index.scss";

const CounterPage = () => {
  const count = useSelector(counterSelector) // 读取 count 值
  const dispatch = useDispatch() // 获得 dispatch,结合 action 就可更新 state

  return (
    <div className="counter-page">
      <div className="counter">
        {/* 同步 - */}
        <div className="btn" onClick={() => dispatch(decrement())}>
          -
        </div>
        <div className="val">{`${count}`}</div>
        {/* 异步 + */}
        <div className="btn" onClick={() => dispatch(incrementAsync(5))}>
          +
        </div>
      </div>
    </div>
  );
};

export default CounterPage;

实际效果:

计时器效果演示

纵观整个案例,相较于不使用 @redux/toolkit 显著提升了研发的效率,降低了研发的使用心智负担!

三、扩展知识

3.1 @redux/toolkit API

在上述的实际案例中,用到了如下 API:

  • configureStore(): 简化 Store 的创建,默认创建了执行异步的中间件,自动启用 redux devtool
  • combineReducers(): 简化合并 reducer 的操作,并自动注入 stateaction
  • createSlice(): 简化并统一创建 action creatorreducer

上述仨 API 可以满足大部分的场景,在此工具辅助下,极大程度上减少了 TypeScript 类型定义的工作。

当然,想要了解更多关于 @redux/toolkit 便捷的 API,推荐阅读官方文档:

  • @redux/tookit 的 API 使用手册[2]
  • @redux/tookit 的 API 使用手册 —— TypeScript 类型相关[3]

3.2 Redux 的状态变更

如果对 Redux 的状态更新过程和原理感兴趣,这里十分推荐阅读:

  • Redux如何实现state变化触发页面渲染?[4]

3.3 Redux 的同步和异步数据流

同步数据流:

Redux 的同步数据流动图链接:https://umapu.cn/imgs/202203/8c767817cfd66ba6c45276c52e98c8b2.gif

异步数据流:

Redux 的异步数据流动图链接:https://umapu.cn/imgs/202203/e7edf1f729772323b2aebaae824716eb.gif

四、总结

React 项目选择 Redux 作为全局的状态管理还是非常推荐的,结合 React 16.x 的 Hooks 状态更新,非常方便,也符合函数组件的编码风格,再瞅瞅 ReactuseContextuseReducer,是不是会有一种 ReactRedux 就是俩亲兄弟的感觉???

简单总结一下:

  1. 推荐在 React 项目中使用 Redux 作为状态管理
  2. 需要掌握 Redux 中的设计思想
  3. 推荐使用 @redux-toolkit,可降低心智负担,显著提升研发效率
  4. 当掌握 @redux-toolkit 后,可补充阅读 Redux 原本的 API,思考一下为什么 @redux-toolkit 要这么做?

参考资料

[1]create-react-app: https://create-react-app.dev

[2]@redux/tookit 的 API 使用手册: https://redux-toolkit.js.org/usage/usage-guide

[3]@redux/tookit 的 API 使用手册 —— TypeScript 类型相关: https://redux-toolkit.js.org/usage/usage-with-typescript

[4]Redux如何实现state变化触发页面渲染?: https://juejin.cn/post/6945808822308962317

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-03-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DYBOY 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、Redux 基础
    • 1.1 什么是 Redux ?
      • 1.2 特点
        • 1.3 设计思想
          • (1) 单一数据源
          • (2) 单向数据流(one-way data flow)
          • (3) 不可变性(Immutability)
          • (4) 纯函数更新 state
      • 二、案例实践
        • 2.1 初始化项目
          • 2.2 安装 Redux 相关依赖
            • 2.3 全局 Store 的创建
              • (1) store/index.ts
              • (2) store/reducers/index.ts
              • (3) store/reducers/counter.ts
            • 2.4 组件中读写 Store
            • 三、扩展知识
              • 3.1 @redux/toolkit API
                • 3.2 Redux 的状态变更
                  • 3.3 Redux 的同步和异步数据流
                    • 参考资料
                • 四、总结
                相关产品与服务
                消息队列 TDMQ
                消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档