React Context运行原理深度解析

49 阅读6分钟

🎯 核心概念: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());
}

📊 对比总结

表格

特性ContextZustand
订阅粒度订阅整个 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] 通知订阅者(值变化: 01)
[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 工作原理

  1. 创建阶段

    • createContext() 创建订阅中心
    • 包含 _currentValue 和 _subscribers
  2. 渲染阶段

    • Provider 更新 _currentValue
    • useContext 订阅 Context
  3. 更新阶段

    • Provider value 变化
    • 通知所有订阅者(全部重新渲染)
  4. 优化方式

    • 拆分 State 和 Actions Context
    • 使用 useMemo 缓存 value
    • 使用 React.memo 跳过渲染

Zustand 工作原理

  1. 创建阶段

    • create() 创建 Store
    • 包含 state 和 listeners
  2. 订阅阶段

    • 使用 selector 精确订阅
    • 缓存上次的值
  3. 更新阶段

    • setState 更新 state
    • 对比每个订阅者的值是否变化
    • 只通知变化的订阅者(精确渲染)

现在理解为什么 Zustand 性能更好了吗?  🎓