React Context真的能替代Redux/Zustand吗?

1,866 阅读4分钟

image.png

每次我们团队开一个新项目,或者面试新同学时,总有一个问题会反复出现:

“我们这个项目,用React Context来做状态管理,够不够?还需要上Redux或者Zustand吗?”

这个问题背后:能不能只用React全家桶,不引入任何第三方库,就搞定这一切? 毕竟,Context是官方的,Redux太重,Zustand又是一个需要额外学习的新东西。

干了这么多年,踩了无数因为滥用Context而导致的性能问题的坑之后,我现在的答案非常明确:

并不能,不太可能!

React Context,从来就不是Redux/Zustand的替代品。它们解决的是完全不同的问题。


先搞清楚,Context的初衷是什么?

要理解为什么不能替代,我们得先回到原点,看看React官方设计Context,到底是为了解决什么问题。

Context的初心,是解决 依赖注入(Dependency Injection) 的问题,它的目标是让组件树深处的子孙组件,能够轻松地拿到顶层组件的数据,从而避免属性逐层透传

想象一下这个场景,举个粒子:

// 没用Context前,theme这个属性需要一层一层往下传,非常繁琐
function App() {
  const theme = 'dark';
  return <Toolbar theme={theme} />;
}

function Toolbar({ theme }) {
  return <Button theme={theme} />;
}

function Button({ theme }) {
  return <div className={`button-${theme}`}>Click Me</div>;
}

Context就像一条管道,让Button组件可以直接拿到App组件提供的数据:

const ThemeContext = React.createContext('light');

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar() {
  return <Button />;
}

function Button() {
  const theme = useContext(ThemeContext); // 直接获取!
  return <div className={`button-${theme}`}>Click Me</div>;
}

你看,在这个场景下,theme这个值是不常变化的。Context完美地解决了问题。


如果 Context 用于频繁更新状态呢?

问题就会出现在这里,很多人看到了Context能共享数据的特性,就想当然地用它来做所有全局状态管理的工作,尤其是那些频繁变化的状态。

这时候,Context的弊端暴露出来了:不必要的重新渲染(Re-renders)

当Context的Providervalue发生变化时,所有消费了这个Context的子组件,无论它是否真的用到了那部分变化了的数据,都会发生重新渲染。(React设计如此🤷‍♂️)

举个栗子:

// 一个同时管理用户信息和购物车状态的Context
const AppContext = React.createContext();

function AppProvider({ children }) {
  const [user, setUser] = useState({ name: 'John' });
  const [cart, setCart] = useState({ items: [] });
  
  const value = { user, cart, setCart }; // 每次AppProvider渲染,value都是新对象
  
  return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
}

// UserInfo组件,它只关心user.name
function UserInfo() {
  const { user } = useContext(AppContext);
  console.log('UserInfo re-rendered!');
  return <div>User: {user.name}</div>;
}

// AddToCartButton组件,它只关心setCart
function AddToCartButton() {
  const { setCart } = useContext(AppContext);
  console.log('AddToCartButton re-rendered!');
  
  const handleAddToCart = () => {
    setCart(prev => ({ items: [...prev.items, 'new item'] }));
  };
  
  return <button onClick={handleAddToCart}>Add to Cart</button>;
}

在这个例子里,当你点击Add to Cart按钮时,setCart被调用,AppProvider重新渲染。

你会发现,控制台里,UserInfo re-rendered!AddToCartButton re-rendered! 会同时被打印出来!

即使user对象根本没有变化,但因为value这个包裹对象每次都是一个新的引用,导致所有消费者都被迫重新渲染。UserInfo组件做了一次完全没有意义的render。在一个大型应用里,这种不必要的渲染,会迅速累积,最终导致严重的性能问题。


Redux/Zustand是怎么解决这个问题的?

image.png

Redux和Zustand这类专业的状态管理库,它们的核心在于 选择器(Selector)订阅(Subscription)

它们从根本上解决了Context的痛点:让组件只订阅它真正关心的数据。

我们用更简洁的Zustand来举例:

// store.js
import { create } from 'zustand';

const useAppStore = create(set => ({
  user: { name: 'John' },
  cart: { items: [] },
  addToCart: () => set(state => ({ 
    cart: { items: [...state.cart.items, 'new item'] } 
  })),
}));

// UserInfo.jsx
function UserInfo() {
  // 关键:传入一个“选择器”函数
  const userName = useAppStore(state => state.user.name); 
  console.log('UserInfo re-rendered!');
  return <div>User: {userName}</div>;
}

// AddToCartButton.jsx
function AddToCartButton() {
  const addToCart = useAppStore(state => state.addToCart);
  console.log('AddToCartButton re-rendered!');
  return <button onClick={addToCart}>Add to Cart</button>;
}

现在,当你点击按钮,调用addToCart时,只有cart状态发生了变化。

Zustand内部会检查,发现<UserInfo />组件订阅的state.user.name并没有发生变化,所以它根本不会通知<UserInfo />去重新渲染

你会在控制台里,只看到AddToCartButton re-rendered!(因为它订阅了addToCart函数,函数地址不变,通常只在初始化时渲染一次)。

这就是专业状态管理库的核心价值:通过精确的订阅和选择,实现最小范围的更新,避免性能浪费。


我的选择标准:一个简单的经验

作为组长,我给我们团队制定了非常清晰的使用标准:

什么时候应该用React Context

  • 低频更新的、全局性的数据:比如主题(Theme)、用户认证信息(用户名、角色)、国际化(i18n)配置等。这些数据,通常在应用初始化后,就不会再变了,或者变得非常少。
  • 纯粹的依赖注入:向下层组件传递一个不会变的API客户端实例、一个日志工具实例等。

什么时候应该用Zustand(或Redux)?

  • 高频更新的、复杂的全局状态:比如购物车、复杂的表单状态、协同编辑应用的数据、来自WebSocket的实时数据等。
  • 组件之间共享的、需要精细化控制渲染的状态
  • 需要使用中间件等高级功能:比如日志记录、异步Action处理等。

React Context是一个官方依赖注入工具Zustand/Redux是一个状态管理器

Context当成状态管理器,偶尔也能用,但大多数时候,会把事情搞得一塌糊涂,结果不是很理想。

作为web开发者,知道什么时候该用哪个,才是专业的体现。

你们说呢😁