🎯 核心概念:Context 是什么?
简单类比
ini
Context = React 内置的"订阅-发布系统"
就像微信群聊:
1. Provider = 群主(发布消息)
2. Consumer/useContext = 群成员(订阅消息)
3. Context Value = 群消息内容
4. 群主发消息 → 所有群成员都能收到
🔧 Context 底层原理(逐步拆解)
第 1 步:创建 Context
typescript
// packages/main-app/src/contexts/DemoContext.tsx
// ✅ 1. 创建 Context(创建"微信群")
import { createContext } from 'react';
interface AppState {
count: number;
}
// createContext 返回一个对象,包含 Provider 和 Consumer
const AppContext = createContext<AppState | null>(null);
// React 内部实现(简化版):
function createContext(defaultValue) {
const context = {
_currentValue: defaultValue, // 当前值
Provider: null, // 后面会创建
Consumer: null, // 后面会创建
_subscribers: new Set(), // 订阅者列表(关键!)
};
return context;
}
第 2 步:Provider 工作原理
typescript
// ✅ 2. Provider 组件("群主")
const App = () => {
const [count, setCount] = useState(0);
return (
<AppContext.Provider value={{ count }}>
<Child />
</AppContext.Provider>
);
};
// React 内部实现(简化版):
function Provider({ value, children }) {
// ① 保存 value 到 Context 对象
context._currentValue = value;
// ② 当 value 变化时,通知所有订阅者
useEffect(() => {
// 遍历所有订阅者,触发重新渲染
context._subscribers.forEach(subscriber => {
subscriber.forceUpdate(); // 强制订阅者重新渲染
});
}, [value]);
return children;
}
第 3 步:useContext 工作原理
typescript
// ✅ 3. useContext Hook("群成员")
const Child = () => {
const { count } = useContext(AppContext);
return <div>Count: {count}</div>;
};
// React 内部实现(简化版):
function useContext(context) {
const fiber = getCurrentFiber(); // 获取当前组件的 Fiber 节点
// ① 将当前组件添加到订阅者列表
useEffect(() => {
context._subscribers.add(fiber);
return () => {
// 组件卸载时取消订阅
context._subscribers.delete(fiber);
};
}, []);
// ② 返回当前 Context 的值
return context._currentValue;
}
🎨 可视化:Context 运行流程
场景 1:初始渲染
yaml
Step 1: 创建 Context
┌─────────────────────┐
│ AppContext │
│ _currentValue: null │
│ _subscribers: [] │
└─────────────────────┘
Step 2: Provider 渲染
┌─────────────────────┐
│ <Provider value=0> │
└─────────────────────┘
│
↓ 设置 _currentValue
┌─────────────────────┐
│ AppContext │
│ _currentValue: 0 │ ← 更新
│ _subscribers: [] │
└─────────────────────┘
Step 3: Child 调用 useContext
┌─────────────────────┐
│ <Child /> │
│ useContext() │
└─────────────────────┘
│
↓ 订阅
┌─────────────────────┐
│ AppContext │
│ _currentValue: 0 │
│ _subscribers: [ │
│ Child Fiber ✅ │ ← Child 订阅了
│ ] │
└─────────────────────┘
│
↓ 返回值
┌─────────────────────┐
│ <Child /> │
│ count = 0 ✅ │ ← 得到值
└─────────────────────┘
场景 2:状态更新
ini
Step 1: 调用 setCount(1)
┌─────────────────────┐
│ <Provider value=1> │ ← value 变化
└─────────────────────┘
│
↓ 更新 _currentValue
┌─────────────────────┐
│ AppContext │
│ _currentValue: 1 │ ← 更新为 1
│ _subscribers: [ │
│ Child Fiber │
│ ] │
└─────────────────────┘
│
↓ 通知订阅者
┌─────────────────────┐
│ Child Fiber │
│ forceUpdate() 🔄 │ ← 触发重新渲染
└─────────────────────┘
│
↓ 重新调用 useContext
┌─────────────────────┐
│ <Child /> │
│ count = 1 ✅ │ ← 得到新值
└─────────────────────┘
🚨 Context 的性能问题
问题:所有订阅者都会重新渲染
typescript
// 场景:多个组件订阅同一个 Context
const GlobalContext = createContext({ theme: 'light', language: 'zh-CN' });
const ThemeButton = () => {
const { theme } = useContext(GlobalContext); // 订阅整个 Context
return <button>{theme}</button>;
};
const LanguageButton = () => {
const { language } = useContext(GlobalContext); // 订阅整个 Context
return <button>{language}</button>;
};
// ❌ 问题:改变 theme 时
setTheme('dark');
// Context 通知所有订阅者:
Context._subscribers.forEach(subscriber => {
subscriber.forceUpdate(); // ThemeButton ✅ 需要重新渲染
// LanguageButton ❌ 不需要但也重新渲染了
});
解决方案:拆分 Context
typescript
// ✅ 方案 1:拆分 State 和 Actions
const StateContext = createContext({ theme: 'light', language: 'zh-CN' });
const ActionsContext = createContext({ setTheme, setLanguage });
const Header = () => {
// 只订阅 Actions(引用不变)
const { setLanguage } = useContext(ActionsContext);
return <LanguageSelector onChange={setLanguage} />;
// ✅ 改变 theme 时,Header 不重新渲染
};
// ✅ 方案 2:使用 useMemo 缓存 value
const App = () => {
const [state, setState] = useState({ theme: 'light', language: 'zh-CN' });
const stateValue = useMemo(() => state, [state]);
const actionsValue = useMemo(() => ({
setTheme: (theme) => setState(s => ({ ...s, theme })),
setLanguage: (lang) => setState(s => ({ ...s, language: lang })),
}), []); // ← actions 引用永远不变
return (
<StateContext.Provider value={stateValue}>
<ActionsContext.Provider value={actionsValue}>
<App />
</ActionsContext.Provider>
</StateContext.Provider>
);
};
🆚 Context vs Zustand 底层对比
Context 实现原理
typescript
// Context = 手动订阅-发布系统
function createContext(defaultValue) {
return {
_currentValue: defaultValue,
_subscribers: new Set(), // 订阅者列表
// 通知所有订阅者
notify() {
this._subscribers.forEach(sub => sub.forceUpdate());
},
};
}
// useContext = 订阅整个 Context
function useContext(context) {
const fiber = getCurrentFiber();
useEffect(() => {
context._subscribers.add(fiber); // 订阅
return () => context._subscribers.delete(fiber);
}, []);
return context._currentValue; // 返回整个值
}
Zustand 实现原理
typescript
// Zustand = 自动精确订阅系统
function create(initializer) {
let state = initializer(); // 初始状态
const listeners = new Set(); // 监听器列表
const setState = (partial) => {
const newState = { ...state, ...partial };
// ✅ 精确对比变化(关键!)
listeners.forEach(listener => {
const { selector, callback } = listener;
const oldValue = selector(state);
const newValue = selector(newState);
// 只有订阅的部分变化了,才触发重新渲染
if (oldValue !== newValue) {
callback();
}
});
state = newState;
};
const subscribe = (selector, callback) => {
listeners.add({ selector, callback });
return () => listeners.delete({ selector, callback });
};
return {
getState: () => state,
setState,
subscribe,
};
}
// useStore = 精确订阅
function useStore(store, selector) {
const [, forceUpdate] = useReducer(x => x + 1, 0);
useEffect(() => {
// ✅ 只订阅 selector 返回的值
return store.subscribe(selector, forceUpdate);
}, []);
return selector(store.getState());
}
📊 对比总结
表格
| 特性 | Context | Zustand |
|---|---|---|
| 订阅粒度 | 订阅整个 Context | 精确订阅任意字段 |
| 变化检测 | 手动 useMemo | 自动对比(Object.is) |
| 通知机制 | 通知所有订阅者 | 只通知变化相关的订阅者 |
| 性能优化 | 需要手动拆分 Context | 自动优化 |
| 代码复杂度 | 需要拆分 State/Actions | 一个 Store 搞定 |
🎓 深入示例:手写简化版 Context
typescript
// packages/main-app/src/examples/SimpleContext.tsx
// ============================================
// 手写 Context 实现(简化版)
// ============================================
type Subscriber = () => void;
class SimpleContext<T> {
private _value: T;
private _subscribers = new Set<Subscriber>();
constructor(defaultValue: T) {
this._value = defaultValue;
}
// Provider 更新值
setValue(newValue: T) {
console.log('[SimpleContext] 更新值:', this._value, '→', newValue);
this._value = newValue;
this.notify();
}
// 获取当前值
getValue(): T {
return this._value;
}
// 订阅
subscribe(callback: Subscriber) {
console.log('[SimpleContext] 新增订阅者');
this._subscribers.add(callback);
// 返回取消订阅函数
return () => {
console.log('[SimpleContext] 取消订阅');
this._subscribers.delete(callback);
};
}
// 通知所有订阅者
private notify() {
console.log(`[SimpleContext] 通知 ${this._subscribers.size} 个订阅者`);
this._subscribers.forEach(callback => callback());
}
}
// ============================================
// 使用示例
// ============================================
import React, { useState, useEffect, useReducer } from 'react';
// 创建 Context 实例
const counterContext = new SimpleContext({ count: 0 });
// Provider 组件
const CounterProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [count, setCount] = useState(0);
// 同步到 Context
useEffect(() => {
counterContext.setValue({ count });
}, [count]);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>增加</button>
<button onClick={() => setCount(c => c - 1)}>减少</button>
<div>Provider 内部 count: {count}</div>
{children}
</div>
);
};
// Consumer 组件
const CounterDisplay: React.FC = () => {
const [, forceUpdate] = useReducer(x => x + 1, 0);
useEffect(() => {
// 订阅 Context 变化
const unsubscribe = counterContext.subscribe(() => {
console.log('[CounterDisplay] 收到通知,重新渲染');
forceUpdate();
});
return unsubscribe;
}, []);
const { count } = counterContext.getValue();
return (
<div>
<h3>CounterDisplay</h3>
<div>显示 count: {count}</div>
</div>
);
};
// 另一个 Consumer(演示"全部重新渲染"问题)
const OtherComponent: React.FC = () => {
const [, forceUpdate] = useReducer(x => x + 1, 0);
useEffect(() => {
const unsubscribe = counterContext.subscribe(() => {
console.log('[OtherComponent] 收到通知,重新渲染(即使不需要)');
forceUpdate();
});
return unsubscribe;
}, []);
return (
<div>
<h3>OtherComponent</h3>
<div>我不需要 count,但也被迫重新渲染了 😢</div>
</div>
);
};
// 使用
export const ContextDemo: React.FC = () => {
return (
<div>
<h1>手写 Context 示例</h1>
<CounterProvider>
<CounterDisplay />
<OtherComponent />
</CounterProvider>
</div>
);
};
运行结果:
css
点击"增加"按钮:
[SimpleContext] 更新值: { count: 0 } → { count: 1 }
[SimpleContext] 通知 2 个订阅者
[CounterDisplay] 收到通知,重新渲染 ✅
[OtherComponent] 收到通知,重新渲染(即使不需要) ❌
🎓 深入示例:手写简化版 Zustand
typescript
// packages/main-app/src/examples/SimpleZustand.ts
// ============================================
// 手写 Zustand 实现(简化版)
// ============================================
type Selector<T, U> = (state: T) => U;
type Listener = () => void;
interface SubscriberInfo<T> {
selector: Selector<T, any>;
listener: Listener;
lastValue: any;
}
class SimpleStore<T> {
private state: T;
private subscribers = new Set<SubscriberInfo<T>>();
constructor(initialState: T) {
this.state = initialState;
}
// 获取状态
getState(): T {
return this.state;
}
// 更新状态
setState(partial: Partial<T>) {
console.log('[SimpleStore] 更新状态:', this.state, '→', { ...this.state, ...partial });
const newState = { ...this.state, ...partial };
// ✅ 精确通知(关键!)
this.subscribers.forEach(({ selector, listener, lastValue }) => {
const newValue = selector(newState);
// 只有订阅的部分变化了,才通知
if (newValue !== lastValue) {
console.log(`[SimpleStore] 通知订阅者(值变化: ${lastValue} → ${newValue})`);
// 更新缓存的值
(this.subscribers as any).forEach((sub: SubscriberInfo<T>) => {
if (sub.listener === listener) {
sub.lastValue = newValue;
}
});
listener();
} else {
console.log(`[SimpleStore] 跳过订阅者(值未变化: ${lastValue})`);
}
});
this.state = newState;
}
// 精确订阅
subscribe<U>(selector: Selector<T, U>, listener: Listener) {
const subscriberInfo: SubscriberInfo<T> = {
selector,
listener,
lastValue: selector(this.state),
};
console.log('[SimpleStore] 新增订阅:', selector.toString());
this.subscribers.add(subscriberInfo);
return () => {
console.log('[SimpleStore] 取消订阅');
this.subscribers.delete(subscriberInfo);
};
}
}
// ============================================
// 使用示例
// ============================================
import React, { useEffect, useReducer } from 'react';
// 创建 Store
interface AppState {
count: number;
text: string;
}
const store = new SimpleStore<AppState>({
count: 0,
text: 'Hello',
});
// Hook:精确订阅
function useStore<U>(selector: Selector<AppState, U>): U {
const [, forceUpdate] = useReducer(x => x + 1, 0);
useEffect(() => {
return store.subscribe(selector, forceUpdate);
}, []);
return selector(store.getState());
}
// 组件 1:只订阅 count
const CounterDisplay: React.FC = () => {
const count = useStore(state => state.count); // ✅ 只订阅 count
console.log('[CounterDisplay] 渲染');
return (
<div>
<h3>CounterDisplay</h3>
<div>Count: {count}</div>
</div>
);
};
// 组件 2:只订阅 text
const TextDisplay: React.FC = () => {
const text = useStore(state => state.text); // ✅ 只订阅 text
console.log('[TextDisplay] 渲染');
return (
<div>
<h3>TextDisplay</h3>
<div>Text: {text}</div>
</div>
);
};
// 控制器
const Controller: React.FC = () => {
return (
<div>
<button onClick={() => store.setState({ count: store.getState().count + 1 })}>
增加 Count
</button>
<button onClick={() => store.setState({ text: store.getState().text + '!' })}>
修改 Text
</button>
</div>
);
};
// 使用
export const ZustandDemo: React.FC = () => {
return (
<div>
<h1>手写 Zustand 示例</h1>
<Controller />
<CounterDisplay />
<TextDisplay />
</div>
);
};
运行结果:
csharp
点击"增加 Count":
[SimpleStore] 更新状态: { count: 0, text: 'Hello' } → { count: 1, text: 'Hello' }
[SimpleStore] 通知订阅者(值变化: 0 → 1)
[CounterDisplay] 渲染 ✅
[SimpleStore] 跳过订阅者(值未变化: Hello)
[TextDisplay] 不渲染 ✅
点击"修改 Text":
[SimpleStore] 更新状态: { count: 1, text: 'Hello' } → { count: 1, text: 'Hello!' }
[SimpleStore] 跳过订阅者(值未变化: 1)
[CounterDisplay] 不渲染 ✅
[SimpleStore] 通知订阅者(值变化: Hello → Hello!)
[TextDisplay] 渲染 ✅
🎯 关键差异总结
Context 的"全局广播"
typescript
// Context = 微信群聊
群主发消息:"count 变成 1 了"
↓
所有群成员都收到通知(即使你只关心 text)
↓
所有组件重新渲染 ❌
Zustand 的"精确推送"
typescript
// Zustand = 个性化推送
Store 更新:"count 变成 1 了"
↓
检查订阅者 1:订阅了 count → 通知 ✅
检查订阅者 2:订阅了 text → 不通知 ✅
↓
只有相关组件重新渲染 ✅
🎓 React 内部:Fiber 架构与订阅
Context 在 Fiber 树中的传播
markdown
Fiber 树结构:
App (Provider)
/ \
Child1 Child2
/ \
useContext useContext
当 Provider value 变化:
1. React 标记 Provider 为 "dirty"
2. 遍历 Fiber 树,找到所有 useContext 的节点
3. 标记这些节点为 "dirty"
4. 触发协调(Reconciliation)
5. 重新渲染这些组件
✅ 学习总结
Context 工作原理
-
创建阶段:
createContext()创建订阅中心- 包含
_currentValue和_subscribers
-
渲染阶段:
Provider更新_currentValueuseContext订阅 Context
-
更新阶段:
Providervalue 变化- 通知所有订阅者(全部重新渲染)
-
优化方式:
- 拆分 State 和 Actions Context
- 使用
useMemo缓存 value - 使用
React.memo跳过渲染
Zustand 工作原理
-
创建阶段:
create()创建 Store- 包含
state和listeners
-
订阅阶段:
- 使用
selector精确订阅 - 缓存上次的值
- 使用
-
更新阶段:
setState更新 state- 对比每个订阅者的值是否变化
- 只通知变化的订阅者(精确渲染)
现在理解为什么 Zustand 性能更好了吗? 🎓