動態

詳情 返回 返回

React中useContext的基本使用和原理解析 - 動態 詳情

React 中 useContext 的使用方法

在 React 中,useContext 是一個內置的 Hook,用於在函數組件中輕鬆訪問 Context(全局公共狀態),避免了手動逐層傳遞 props 的複雜性。它依賴於 Context API,通過 Provider 提供數據,後代組件通過 useContext 消費數據。以下是詳細的使用方法和步驟,基於 React 官方指南和實踐經驗。

1. 創建 Context 對象

首先需要使用 React.createContext 創建一個 Context 對象。這個對象包含 ProviderConsumer 組件,但 useContext 簡化了消費過程。

import React from 'react';
// 創建Context,可設置默認值(可選)
const MyContext = React.createContext(defaultValue);
  • defaultValue 是當組件上方無 Provider 時的回退值,通常設為 null 或初始狀態。

2. 使用 Provider 提供數據

在父組件中,用 <Context.Provider> 包裹子組件,並通過 value 屬性傳遞數據。Provider 必須位於調用 useContext 的組件之上。

import React from 'react';
import ChildComponent from './ChildComponent';
import MyContext from './MyContext';

function ParentComponent() {
  const sharedData = { theme: 'dark', user: 'Alice' }; // 共享數據
  return (
    <MyContext.Provider value={sharedData}>
      <ChildComponent /> {/* 後代組件可訪問sharedData */}
    </MyContext.Provider>
  );
}
  • 注意​:Provider 的 value 變化時,所有消費該 Context 的組件會自動重新渲染​​。

3. 在後代組件中使用 useContext 消費數據

在後代組件中,導入 Context 對象並調用 useContext,直接獲取 Provider 提供的 value

import React, { useContext } from 'react';
import MyContext from './MyContext'; // 導入父組件中的Context

function ChildComponent() {
  const publicData = useContext(MyContext); // 調用useContext獲取數據
  return (
    <div>
      <p>當前主題: {publicData.theme}</p>
      {/* 示例:渲染圖片或其他UI */}
      <img src="image-path" alt="示例" style={{ width: '50px', marginLeft: '10px' }} />
    </div>
  );
}
  • 關鍵點​:

    • useContext(MyContext) 返回最近的 Provider 的 value;若無 Provider,則返回 defaultValue
    • 代碼簡潔,無需嵌套 <Context.Consumer>
    • Context 變化時,React 會觸發組件重新渲染,確保數據最新​​。

4. 完整代碼示例

整合以上步驟,一個簡單應用:

// 文件: Context.js
import React from 'react';
export const ThemeContext = React.createContext({ theme: 'light' });

// 文件: App.js (父組件)
import React from 'react';
import { ThemeContext } from './Context';
import Child from './Child';

function App() {
  return (
    <ThemeContext.Provider value={{ theme: 'dark' }}>
      <Child />
    </ThemeContext.Provider>
  );
}

// 文件: Child.js (後代組件)
import React, { useContext } from 'react';
import { ThemeContext } from './Context';

function Child() {
  const { theme } = useContext(ThemeContext);
  return <div>當前主題: {theme}</div>; // 輸出: 當前主題: dark
}

在類組建中,useContext 的使用方法

在類組件中使用 Context 有兩種方式:

  1. 使用 static contextType 屬性(只能訂閲單一 Context)
  2. 使用 Context.Consumer(可訂閲多個 Context)

而在函數組件中,我們使用 useContext 鈎子(可訂閲多個 Context)。

下面我將詳細説明類組件中使用 Context 的方法,並對比函數組件中的使用差異。

1、使用 static contextType(單一 Context 訂閲)

步驟:

  • 創建 Context:const MyContext = React.createContext(defaultValue);
  • 在類組件中通過 static contextType = MyContext; 指定要訂閲的 Context
  • 通過 this.context 訪問 Context 的值

示例代碼

import React from 'react';

// 創建Context
const ThemeContext = React.createContext('light');

class MyClassComponent extends React.Component {
  static contextType = ThemeContext; // 關鍵:靜態屬性賦值

  render() {
    const theme = this.context; // 通過this.context訪問
    return <div>當前主題: {theme}</div>;
  }
}

// 在父組件中提供Context
function App() {
  return (
    <ThemeContext.Provider value="dark">
      <MyClassComponent />
    </ThemeContext.Provider>
  );
}

2、使用 Context.Consumer(支持多個 Context)

步驟:

  • 在類組件的 render 方法中,使用 <MyContext.Consumer> 組件包裹
  • 內部使用函數作為子元素(render prop 模式)

示例代碼:

import React from 'react';

// 創建兩個Context
const ThemeContext = React.createContext('light');
const UserContext = React.createContext('Guest');

class MyClassComponent extends React.Component {
  render() {
    return (
      // 消費多個Context
      <ThemeContext.Consumer>
        {theme => (
          <UserContext.Consumer>
            {user => (
              <div>
                主題: {theme}, 用户: {user}
              </div>
            )}
          </UserContext.Consumer>
        )}
      </ThemeContext.Consumer>
    );
  }
}

// 在父組件中提供多個Context
function App() {
  return (
    <ThemeContext.Provider value="dark">
      <UserContext.Provider value="Alice">
        <MyClassComponent />
      </UserContext.Provider>
    </ThemeContext.Provider>
  );
}

類組件與函數組件使用 Context 的主要區別

特性 類組件 函數組件
訂閲方式 1. static contextType + this.context(單一)2. Context.Consumer(支持多個) useContext 鈎子(支持多個)
多個 Context 使用 使用 Context.Consumer 嵌套較深 直接多次調用 useContext,簡潔清晰
代碼簡潔性 相對冗長,尤其是多個 Context 時 非常簡潔
組件類型限制 static contextType 僅適用於類組件(引用[1]) useContext 僅適用於函數組件
動態更新 當 Context 更新時,組件都會重新渲染 同樣重新渲染,但可通過 React.memo 優化

useContext 實現原理詳解

useContext 的實現原理基於 React 的 上下文機制(Context) 和 ​訂閲-發佈模式​,主要涉及三個核心環節:

1. Context 對象的內部結構

每個通過 createContext() 創建的 Context 對象包含以下關鍵屬性:

const MyContext = React.createContext(defaultValue);
// 內部結構:
{
  _currentValue: defaultValue,  // 當前值存儲
  _threaded: true,             // 標識當前渲染線程
  Provider: { ... },           // Provider 組件
  Consumer: { ... },           // Consumer 組件
  _currentRenderer: null,      // 當前渲染器
  _globalName: null,           // 全局名稱
  _subscribe: function() { ... } // 訂閲函數
}

核心是 _currentValue (存儲當前值) 和 _subscribe (管理訂閲者鏈表)

2. 值讀取與訂閲機制

當調用 useContext(MyContext) 時:

function useContext(Context) {
  // 1. 從 Context._currentValue 讀取當前值
  const value = readContext(Context); 
  
  // 2. 將當前組件添加到訂閲鏈表
  subscribeToContext(Context, currentlyRenderingFiber);
  
  return value; // 返回上下文值
}

currentlyRenderingFiber 是 React 內部的一個​全局變量​,用於指向當前正在執行的函數組件所對應的 ​Fiber 節點​。它的主要作用是在函數組件渲染過程中為 Hooks 提供訪問當前組件狀態的橋樑。

具體過程:

  1. 讀取值​:直接訪問 Context._currentValue 獲取最新值
  2. 建立訂閲​:將當前函數組件對應的 Fiber 節點添加到 Context 的訂閲者鏈表

    • 通過 currentlyRenderingFiber.dependencies 鏈表維護訂閲關係
    • 每個依賴項包含 context 指針和訂閲狀態

3. 更新觸發流程

當 Provider 的值更新時:

<MyContext.Provider value={newValue}>
// 1. 更新 Context._currentValue = newValue
// 2. 遍歷訂閲者鏈表 (Context._subscribe)
// 3. 標記所有訂閲組件的 Fiber 節點為需要更新
// 4. 觸發重新渲染

關鍵點:

  • 批量更新​:React 會合並多個 Context 更新,避免頻繁渲染
  • 精準更新​:只更新訂閲該 Context 的組件(通過 Fiber 依賴鏈)
  • 默認值處理​:無 Provider 時返回 createContext(defaultValue) 的默認值
user avatar grewer 頭像 ting_61d6d9790dee8 頭像 linlinma 頭像 front_yue 頭像 littlelyon 頭像 hard_heart_603dd717240e2 頭像 shuirong1997 頭像 xiaodiandideyangrouchuan 頭像 nznznz 頭像 yangxiansheng_5a1b9b93a3a44 頭像 user_ze46ouik 頭像 b_a_r_a_n 頭像
點贊 39 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.