React Native 状态管理大比拼:Event Bus 还是 Context?小白一看就懂!

React Native 状态管理大比拼:Event Bus 还是 Context?小白一看就懂!

在 React Native 开发中,当我们想让两个相隔十万八千里的组件"隔空对话"时,经常会面临一个灵魂拷问:到底是该用 Event Bus(事件总线),还是用 React Context(上下文)?

很多新手觉得这俩东西差不多,都能实现"全局通信",随便用哪个都行。但其实它们就像是**"对讲机""公告板"**的区别,用错了场景,后期的代码维护会让你痛不欲生。

今天,我们就用最通俗易懂的语言,来把这俩兄弟扒个底朝天!


1. 核心比喻:对讲机 VS 公告板

要理解它们的区别,只要记住这个比喻:

Event Bus (事件总线)= 对讲机 📻

  • 怎么工作:你按下按钮喊一句"洞幺洞幺,收到请回答",这句话瞬间发出去。只有当时开着对讲机、调到同一频道的人才能听到。
  • 特点传的是"动作"。喊完就没了,没有记忆。如果你喊的时候,对方正好把对讲机关了,那他永远都不知道你刚才说了什么。

React Context = 公告板 📋

  • 怎么工作:你在村口的公告板上贴了一张纸:"今天中午吃红烧肉"。
  • 特点传的是"状态/数据"。它是有记忆的。不管村民是上午来看,还是下午来看,只要公告没换,大家看到的都是"红烧肉"。

2. 深度对比:它们各有什么优缺点?

对比维度 Event Bus (对讲机) React Context (公告板)
性能 极高 🚀 <br>发通知只让监听了的人干活,其他人该干嘛干嘛。 较差 🐢(容易踩坑) <br>公告板一换内容,所有盯着公告板的人(组件)都会重新刷新(渲染)。
使用范围 无限制 🌍 <br>可以在 React 组件里用,也能在普通的 .js 工具文件里用(比如网络请求报错时)。 局限于 React 组件树 🌳 <br>必须包在 <Provider> 标签里,只能在组件内部读取。
记性(状态持久性) 鱼的记忆 🐟 <br>事件发出去那一瞬间,错过了就错过了。 大象的记忆 🐘 <br>数据一直都在,新打开的页面也能随时拿到最新数据。
清理麻烦度 必须手动清理 🧹 <br>页面销毁时如果不关掉监听,后台会一直运行,导致内存泄漏甚至重复报错。 全自动 🤖 <br>React 自己会管,页面关了就自动解绑,不用你操心。

3. 实战场景:到底该怎么选?

看了上面的对比,你可能还是有点懵。我们直接上具体的业务场景!

场景一:用户登录状态、App主题色

选择:React Context ✅

为什么? 因为这些是**"全局需要共享的静态/低频变化数据"**。不管用户跳到哪个页面,页面一打开就需要知道"我现在是谁?"、"我是黑夜模式还是白天模式?"。公告板模式(Context)最适合这种需要持久记忆的场景。

场景二:全局硬件扫码、Token过期被强制踢下线

选择:Event Bus ✅

为什么? 因为扫码是一个**"瞬间的动作"**。 比如:用户拿扫码枪扫了一个二维码。我们只需要在全局监听这个动作,然后通知当前页面去更新 UI。如果我们把"最新扫码结果"存到 Context 里,那每次扫码都会导致一堆无关的组件跟着重新渲染,非常浪费性能。而且,与硬件通信,底层天然就是基于事件的。

场景三:点击商品列表的"加入购物车",通知底部 TabBar 更新角标数量

选择:React Context (或者 Redux/Zustand) ✅

为什么? 很多新手这里会用 Event Bus,发一个"购物车数量+1"的事件。但这有个致命缺陷:如果你还没打开过 TabBar,或者从别的页面跳回来,角标数量就对不上了。因为购物车数据是"状态",必须持久保存,所以应该存在 Context 或者专门的状态管理库里。


4. 代码演示(极简版)

Event Bus 实战:全局订单扫码与串口刷卡(以 React Native 为例)

在智能收银机、工业平板等场景中,我们经常需要处理硬件设备的输入。用 RN 自带的 DeviceEventEmitter 来做事件总线是最优解。

案例 1:全局订单扫码状态同步

假设用户用扫码枪扫了一个订单条码,我们需要把订单状态从"制作中"变更为"请取餐"。

javascript 复制代码
import { DeviceEventEmitter } from 'react-native';

// 1. 定义事件名(好习惯:集中管理)
export const GLOBAL_SCANNER_EVENTS = {
  ORDER_STATUS_CHANGED: 'GLOBAL_ORDER_STATUS_CHANGED',
};

// 2. 发送方:全局的扫码监听服务(后台默默运行)
// 扫码枪扫码后触发
const handleOrderScanned = (barcode) => {
  // 模拟接口调用成功,状态变更为 collecting (请取餐)
  DeviceEventEmitter.emit(GLOBAL_SCANNER_EVENTS.ORDER_STATUS_CHANGED, { 
    orderId: barcode, 
    newStatus: 'collecting' 
  });
};

// 3. 接收方:订单列表页(OrderRecordList)
useEffect(() => {
  // 戴上对讲机,监听订单状态变更
  const subscription = DeviceEventEmitter.addListener(
    GLOBAL_SCANNER_EVENTS.ORDER_STATUS_CHANGED, 
    ({ orderId, newStatus }) => {
      console.log(`订单 ${orderId} 状态变更为 ${newStatus}`);
      if (newStatus === 'collecting') {
        // 更新 UI:把订单从"制作中"移到"请取餐"列表
        moveToCollectingList(orderId);
      }
  });

  // ⚠️ 极其重要:页面销毁时一定要摘下对讲机,防止内存泄漏!
  return () => {
    subscription.remove();
  };
}, []);
案例 2:串口读卡器刷卡监听

如果你的设备连着一个 LF125k 的低频刷卡器,全局监听刷卡动作也是同样的套路:

javascript 复制代码
// 全局发送刷卡事件
const onCardRead = (cardNumber) => {
  DeviceEventEmitter.emit('GLOBAL_CARD_SCANNED', cardNumber);
};

// 在需要响应刷卡的页面监听
useEffect(() => {
  const sub = DeviceEventEmitter.addListener('GLOBAL_CARD_SCANNED', (cardNo) => {
    // 自动填入输入框或者直接发起查询请求
    queryEmployeeInfo(cardNo);
  });
  return () => sub.remove();
}, []);

Context 实战:用户登录状态与权限管理

javascript 复制代码
import React, { createContext, useContext, useState } from 'react';

// 1. 建个公告板
export const AuthContext = createContext();

// 2. 贴公告(放在最顶层组件,比如 App.js)
export const App = () => {
  const [isSignedIn, setIsSignedIn] = useState(false);
  const [userInfo, setUserInfo] = useState(null);

  return (
    <AuthContext.Provider value={{ isSignedIn, setIsSignedIn, userInfo }}>
      <NavigationContainer>
        {/* 如果登录了,才挂载全局扫码监听服务 */}
        {isSignedIn && <GlobalOrderScannerListener />}
        <YourPages />
      </NavigationContainer>
    </AuthContext.Provider>
  );
};

// 3. 看公告(在任何深层子组件里,比如个人中心页)
const ProfilePage = () => {
  // 只要 AuthContext 里的值变了,这个组件就会自动获取最新值并刷新
  const { isSignedIn, userInfo, setIsSignedIn } = useContext(AuthContext);
  
  if (!isSignedIn) return <Text>请先登录</Text>;
  
  return (
    <View>
      <Text>欢迎您,{userInfo?.name}</Text>
      <Button title="退出登录" onPress={() => setIsSignedIn(false)} />
    </View>
  );
};

5. 总结

不要为了用某个技术而用它,最好的架构是"适合"。

  • 要传动作/指令,怕影响性能 ➡️ 选 Event Bus
  • 要存全局数据,需要持久记忆 ➡️ 选 Context

掌握了这个判断标准,你的 React Native 进阶之路就又稳健了一步!

相关推荐
爱滑雪的码农1 小时前
React Native 完整开发全流程(从零到上线)
javascript·react native·react.js
沐言人生2 小时前
ReactNative 源码分析12——Native View创建流程onBatchComplete
android·react native
沐言人生2 天前
ReactNative 源码分析11——Native View创建流程setChildren和manageChildren
android·react native
沐言人生3 天前
ReactNative 源码分析10——Native View创建流程createView
android·react native
坏小虎3 天前
【聊天列表组件选型建议】FlashList、FlatList、LegendList三种列表组件
javascript·react native·react.js
sealaugh324 天前
react native(学习笔记第五课) 英语打卡微应用(4)- frontend的列表展示
笔记·学习·react native
沐言人生5 天前
ReactNative 源码分析9——Native View初始化
android·react native
接着奏乐接着舞5 天前
react native expo打包
javascript·react native·react.js
jxm_csdn6 天前
Expo Go 本地命令行编译 apk(Ubutnu22.04)
react native