React数据流管理深入对比与剖析

文章内容较长,读者可以配合目录和小结阅读。(文末附上全文内容思维导图,按需取用。思维导图真是整理学习的好工具!夸夸)

React Hooks革新了状态管理,但基于Hooks的数据流方案选择太多。本文将深度剖析主流React数据流方案,旨在深入对比澄清其应用场景、设计哲学、优缺点,助你迅速梳理对React数据流管理的认识,锁定最适合的解决方案。

1. 简单Hook(useState+useEffect)

适用场景: 局部状态管理组件内简单的数据管理

优势: 使用简单、没有负担

劣势: 不擅长跨组件、复杂的数据管理

2. useReducer

​适用场景:

局部状态管理, 单组件内有复杂的逻辑处理。

有一种说法是,useReducer是更高级的useState。useReducer可以处理一组件内复杂的数据逻辑,比如数据更新合并

​优势

  • ​1. 支持组件内复杂逻辑处理,可以让数据更新合并

    • 组件内部多个变量,且变量之间有相互依赖/联动逻辑(useState就做不到)
  • ​2. React内置Hook,无需安装依赖

  • ​3. 与useContext结合,使得状态和状态更新都可以在组件树中共享

​劣势

  • 不擅长全局复杂状态管理

场景举例 useReducer VS useState

举一个业务场景例子:

假设我们有一个购物车应用,其中包含多个商品项目,每个项目有数量、单价、是否选中等状态。用户可以增加/减少数量、选择/取消选择商品、删除商品等。

  1. 使用useState
js 复制代码
const [cartItems, setCartItems] = useState([]);
const [selectedItems, setSelectedItems] = useState([]);
const [totalPrice, setTotalPrice] = useState(0);
// 其他状态...

当涉及到一个操作需要改变多个状态时(例如,删除一个商品需要更新 cartItems, selectedItems 和 totalPrice),代码会变得难以管理和维护。

  1. 使useReducer

使用 useReducer,你可以将所有购物车相关的逻辑放在一个地方:

js 复制代码
const initialState = {
  cartItems: [],
  selectedItems: [],
  totalPrice: 0,
};

function cartReducer(state, action) {
  switch (action.type) {
    case 'ADD_ITEM':
      // 逻辑...
      return newState;
    case 'REMOVE_ITEM':
      // 逻辑...
      return newState;
    case 'TOGGLE_ITEM_SELECTION':
      // 逻辑...
      return newState;
    case 'UPDATE_TOTAL_PRICE':
      // 逻辑...
      return newState;
    default:
      return state;
  }
}

const [state, dispatch] = useReducer(cartReducer, initialState);

​设计思想

  • useState + reducer(函数式、发布订阅)

PS: reducer理解 (纯函数)

  1. Reducer 是一个纯函数,用于执行状态转换。它接收当前状态和一个 action(动作),然后返回一个新状态。
  2. Reducer的特点其实就是纯函数,关于这个模式的理解见[[##8.补充知识点]]

3. useContext

​适用场景:简单场景的全局状态管理。组件间共享数据流

​优势: 1. 使用简单

​劣势:

  • ​1. 需要在最外层用provider包裹组件,与UI不解耦

    • hack解决方案:unstated-next
  • ​2. 更新粒度太粗,同上下文中任何值的改变都会导致冲渲染

    • (PS: hack解决方案use-context-selector)

4. Redux

​使用场景:

  • 复杂度较高时,团队规模较大
  • 要需要状态持久化或可回溯的场景

​核心竞争力

  • ​1. 状态持久化(组件销毁也可保留状态)

  • ​2. 状态可回溯(可以基于action打log可以回溯)

    • 比如redux-dev-tools有"时光机"功能
  • ​3. 函数式编程 (纯函数,无副作用)

  • ​4. 中间件支持 (针对异步数据流,社区提供了很多中间件解决方案)

    • redux middleware 将 action 对接到 reducer 的黑盒的控制权暴露给了开发者

​缺点

  • ​1. 要写的代码多、复杂

  • ​2. 只有一个全局store,多组件可能出现store状态残留

    • 多组件共用store里状态要注意初始化清空问题
  • ​3. 无脑发布订阅

    • ​每次dispatch都会遍历所有reducer,更新粒度太粗,渲染浪费

      • 可以搭配useSelector(默认浅比较,可以提供自定义函数)解决
  • ​4. 交互频繁时会有卡顿

  • ​5. 需要搭配其他库一起使用

关于store状态残留具体是什么场景?

Redux 设计之初就是为了提供一个全局的、单一的状态管理容器(Store)。这种设计使得状态管理变得集中但也相对复杂。因为所有的状态都存储在一个全局对象中,所以很容易出现状态残留的问题,特别是在以下几种情况:

  1. 单页应用:在单页应用(SPA)中,页面间的状态切换可能不会触发页面重新加载,导致上一个页面的状态残留。
  2. 组件的动态加载与卸载:当一个组件卸载时,它在 Redux Store 中的状态不会自动清除。如果下次该组件重新加载,它可能会读取到旧的、已经不再相关的状态。
  3. 异步操作:异步操作(如 API 请求)可能会在完成后改变状态,如果没有正确处理,可能会导致状态残留。

例如,假设你有一个购物车页面和一个产品列表页面。在购物车页面,你可能会将一个 isCartVisible 的状态设置为 true。当你导航到产品列表页面时,除非你显式地将 isCartVisible 设置回 false,否则这个状态就会"残留"。

代码示例

假设在购物车组件中有如下逻辑:

js 复制代码
useEffect(() => {
  dispatch({ type: 'SHOW_CART' });  // 设置 isCartVisible 为 true

  return () => {
    dispatch({ type: 'HIDE_CART' });  // 设置 isCartVisible 为 false
  };
}, []);

在这个例子中,useEffect 的清理函数会在组件卸载时运行,将 isCartVisible 设置回 false,从而避免状态残留。

​设计思想

  • 函数式库。发布订阅者模式

5. Mobx​

​Mobx把最简单的操作提供给了用户,其他自己内部实现,主打的响应式设计让我们只关注和操作Observable data

​优势

  • ​1. 代码量少

    • 基于Observable,自动订阅、自动发布,去dispatch
  • ​2. 基于数据劫持。更新粒度细且精准,无需SCU

    • ​redux不能直接修改state,mobx可随意修改

    • redux需要对监听的组件走SCU优化,减少重复render。mobx是智能的。

  • ​3. 多store抽离业务逻辑(Model View分离)

    • redux只有一个store,mobx多store,可以根据功能或业务逻辑将状态分解到不同的 Store 中。
  • ​4. 响应式良好(频繁的交互可以胜任)

​缺点

  • ​1. 没有状态回溯能力 (直接修改对象引用,很难做状态回溯)

  • ​2. 没有很好的异步流解决方案、没有中间件能力

    • mobx、redux都不能很好处理异步数据流,但redux提供了applyMiddleware,而mobx无
  • ​3.多store维护成本高

    • store数增多,维护成本会增加,多store之间数据共享容易出错
    1. 副作用

​设计思想

  • 响应式库,观察者模式(数据劫持Proxy)

​使用场景

  • 复杂度一般时,小规模团队或开发周期较短、要求快速上线时,使用Mobx

​6. Zustand

​优势

  • ​1. 简单易用

  • ​2. 性能优秀,只在需要时重新渲染组件

  • ​3. 与React hooks完美集成,基于Hooks构建,使用非常自然

缺点​

  • ​1. 缺乏中间件支持

  • ​2. 社区不够成熟

  • ​3. 对于非常大和复杂的项目可能不是最佳选择

​设计思想

  • 函数式编程+Hook钩子思想

​7. rxjs

​RxJS把一切抽象成流,提供了非常多且强大的操作符,且无副作用,无论是处理同步还是异步数据流都游刃有余

​优势

  • ​​1. 强大的异步数据流处理,redux、mobx等都不擅长

  • ​2. 纯函数

    • 在数据流动过程中,不会改变已有Observable(可观察对象),会返回一个新的Observable
  • ​3. 有强大的操作符支持异步处理,又称 lodash for async

  • ​4. 独立

    • 不依赖于任何框架(不依赖React,基于js),可以任意搭配

​缺点

  • ​1. 学习曲线陡险

  • ​2. 事件流高度抽象

​设计思想

  • 基于Event Stream,把前端的一切转化为数据源后,

  • 函数式+响应式。观察者模式 + 迭代器模式

总结

​使用场景:

  • 复杂度较高,且数据流(尤其是异步数据)混杂时,使用RxJS

​8. 关于不同数据流管理方式的设计模式的说明

梳理下来,数据流的管理方式主要是用了以下三种设计模式

​1. 函数式编程

  • reducer+ action 模式

      1. 纯函数与不可变性
      • 重点:没有副作用,提供可预测性和易于测试的状态管理。
        • 纯函数:输出完全由输入决定,没有副作用。
        • 数据不可变:状态不被直接修改,而是生成新的状态。
      1. 高阶函数 (connect)
      • 重点:扩展功能,复用逻辑。
        • 如 Redux 的 connect(),用于组件和状态库的连接。
  • rxjs模式

    • 将副作用转化为数据源,将副作用隔离在管道流处理之外

2.响应式编程

  • 数据劫持与观察者模式
    • 特点:实时、精准地响应状态变化
    • 如 MobX 使用 Proxy 进行数据劫持,做到细粒度自动更新视图。

3. 迭代器模式

RxJS 与迭代器模式

Rxjs通过迭代器模式与发布订阅模式结合,提升对异步数据的精准性与可控性。

  1. Observer(订阅者) 接口:定义了 next、error 和 complete 方法。
ini 复制代码
var observer = { 
    next: val => {...}, 
    error: err => {...}, 
    complete: () => {...} 
};
 ​observer.next()
  1. 事件流(Observable发布者): 通过Observales创建事件流,可以发出三种类型通知 next\error\complete
js 复制代码
const observable = new Observable(observer => {
    observer.next('value1');
    observer.next('value2');
    observer.complete();
});
  1. 通过 Observer 接口,每个 Observable 对象可以关联多个 Observer。这些 Observer 通过迭代器模式的 next、error、complete 方法响应 Observable 的状态变化。

  2. 通过这种结合,RxJS 可以对复杂的异步操作流程进行抽象和简化,从而更容易地处理和控制异步数据。

全文思维导图笔记

小结

在这里,对于主流React数据管理方案也介绍完了,从最基础的useState和useEffect,到更加复杂和强大的RxJS模式,各有各的优势和适用场景。本质上,这些数据管理方案都是不同设计模式的体现,而最合适的选择则依赖于具体的业务需求和场景。小结一下场景选择

  1. 局部组件内简单状态管理 -- useState + useEffect
  2. 局部组件内复杂状态管理,如多个相互依赖的状态 -- useReducer
  3. 跨组件状态管理(复杂度一般) ------ useReducer + useContext
  4. 大型应用全局状态管理(复杂度一般)------ zustand/mobx。
  5. 大型应用全局状态管理(项目复杂、需要易回溯易测试) ------ redux
  6. 复杂混乱的状态(设计拖拽等混乱复杂数据流) -- rxjs
相关推荐
金灰1 分钟前
HTML5--裸体回顾
java·开发语言·前端·javascript·html·html5
茶卡盐佑星_4 分钟前
说说你对es6中promise的理解?
前端·ecmascript·es6
Манго нектар32 分钟前
JavaScript for循环语句
开发语言·前端·javascript
蒲公英100139 分钟前
vue3学习:axios输入城市名称查询该城市天气
前端·vue.js·学习
天涯学馆1 小时前
Deno与Secure TypeScript:安全的后端开发
前端·typescript·deno
以对_1 小时前
uview表单校验不生效问题
前端·uni-app
程序猿小D2 小时前
第二百六十七节 JPA教程 - JPA查询AND条件示例
java·开发语言·前端·数据库·windows·python·jpa
奔跑吧邓邓子2 小时前
npm包管理深度探索:从基础到进阶全面教程!
前端·npm·node.js
前端李易安3 小时前
ajax的原理,使用场景以及如何实现
前端·ajax·okhttp
杰哥在此3 小时前
Python知识点:如何使用Multiprocessing进行并行任务管理
linux·开发语言·python·面试·编程