欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

React?Context原理深入理解源碼示例分析

 更新時間:2023年01月03日 15:47:51   作者:flyzz177  
這篇文章主要為大家介紹了React?Context原理深入理解源碼示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

正文

在 React 中提供了一種「數(shù)據(jù)管理」機制:React.context,大家可能對它比較陌生,日常開發(fā)直接使用它的場景也并不多。

但提起 react-redux 通過 Providerstore 中的全局狀態(tài)在頂層組件向下傳遞,大家都不陌生,它就是基于 React 所提供的 context 特性實現(xiàn)。

本文,將從概念、使用,再到原理分析,來理解 Context 在多級組件之間進行數(shù)據(jù)傳遞的機制。

一、概念

Context 提供了一個無需為每層組件手動添加 props,就能在組件樹間進行數(shù)據(jù)傳遞的方法。

通常,數(shù)據(jù)是通過 props 屬性自上而下(由父到子)進行傳遞,但這種做法對于某些類型的屬性而言是極其繁瑣的(例如:地區(qū)偏好,UI 主題),這些屬性是應用程序中許多組件都需要的。

Context 提供了一種在組件之間共享此類值的方式,而不必顯式地通過組件樹的逐層傳遞 props。

設計目的是為了共享那些對于一個組件樹而言是“全局”的數(shù)據(jù),例如當前認證的用戶、主題或首選語言。

二、使用

下面我們以 Hooks 函數(shù)組件為例,展開介紹 Context 的使用。

2.1、React.createContext

首先,我們需要創(chuàng)建一個 React Context 對象。

const Context = React.createContext(defaultValue);

當 React 渲染一個訂閱了這個 Context 對象的組件,這個組件會從組件樹中的 Context.Provider 中讀取到當前的 context.value 值。

當組件所處的樹中沒有匹配到 Provider 時,其 defaultValue 參數(shù)才會生效。

2.2、Context.Provider

每個 Context 對象都會返回一個 Provider React 組件,它接收一個 value 屬性,可將數(shù)據(jù)向下傳遞給消費組件。當 Provider 的 value 值發(fā)生變化時,它內部的所有消費組件都會重新渲染。

注意,當 value 傳遞為一個復雜對象時,若想要更新,必須賦予 value 一個新的對象引用地址,直接修改對象屬性不會觸發(fā)消費組件的重渲染。

<Context.Provider value={/* 某個值,一般會傳遞對象 */}>

2.3、React.useContext

Context Provider 組件提供了向下傳遞的 value 數(shù)據(jù),對于函數(shù)組件,可通過 useContext API 拿到 Context value

const value = useContext(Context);

useContext 接收一個 context 對象(React.createContext 的返回值),返回該 context 的當前值。

當組件上層最近的 <Context.Provider> 更新時,當前組件會觸發(fā)重渲染,并讀取最新傳遞給 Context Provider 的 context value 值。

題外話:React.memo 只會針對 props 做優(yōu)化,如果組件中 useContext 依賴的 context value 發(fā)生變化,組件依舊會進行重渲染。

2.4、Example

我們通過一個簡單示例來熟悉上述 Context 的使用。

const Context = React.createContext(null);
const Child = () => {
  const value = React.useContext(Context);
  return (
    <div>theme: {value.theme}</div>
  )
}
const App = () => {
  const [count, setCount] = React.useState(0);
  return (
    <Context.Provider value={{ theme: 'light' }}>
      <div onClick={() => setCount(count + 1)}>觸發(fā)更新</div>
      <Child />
    </Context.Provider>
  )
}
ReactDOM.render(<App />, document.getElementById('root'));

示例中,在 App 組件內使用 Providervalue 值向子樹傳遞,Child 組件通過 useContext 讀取 value,從而成為 Consumer 消費組件。

三、原理分析

從上面「使用」我們了解到:Context 的實現(xiàn)由三部分組成:

  • 創(chuàng)建 Context:React.createContext() 方法;
  • Provider 組件:<Context.Provider value={value}>
  • 消費 value:React.useContext(Context) 方法。

原理分析脫離不了源碼,下面我們挑選出核心代碼來看看它們的實現(xiàn)。

3.1、createContext 函數(shù)實現(xiàn)

createContext 源碼定義在 react/src/ReactContext.js 位置。它返回一個 context 對象,提供了 ProviderConsumer 兩個組件屬性,_currentValue 會保存 context.value 值。

const REACT_PROVIDER_TYPE = Symbol.for('react.provider');
const REACT_CONTEXT_TYPE = Symbol.for('react.context');
export function createContext<T>(defaultValue: T): ReactContext<T> {
  const context: ReactContext<T> = {
    $$typeof: REACT_CONTEXT_TYPE,
    _calculateChangedBits: calculateChangedBits,
    // 并發(fā)渲染器方案,分為主渲染器和輔助渲染器
    _currentValue: defaultValue,
    _currentValue2: defaultValue,
    _threadCount: 0, // 跟蹤此上下文當前有多少個并發(fā)渲染器
    Provider: (null: any),
    Consumer: (null: any),
  };
  context.Provider = {
    $$typeof: REACT_PROVIDER_TYPE,
    _context: context,
  };
  context.Consumer = context;
  return context;
}

盡管在這里我們只看到要返回一個對象,卻看不出別的名堂,只需記住它返回的對象結構信息即可,我們接著往下看。

3.2、 JSX 編譯

我們所編寫的 JSX 語法在進入 render 時會被 babel 編譯成 ReactElement 對象。我們可以在 babel repl 在線平臺 轉換查看。

JSX 語法最終會被轉換成 React.createElement 方法,我們在 example 環(huán)境下執(zhí)行方法,返回的結果是一個 ReactElement 元素對象。

對象的 props 保存了 context 要向下傳遞的 value,而對象的 type 則保存的是 context.Provider

context.Provider = {
  $$typeof: REACT_PROVIDER_TYPE,
  _context: context,
};

有了對象描述結構,接下來進入渲染流程并在 Reconciler/beginWork 階段為其創(chuàng)建 Fiber 節(jié)點。

3.3、消費組件 - useContext 函數(shù)實現(xiàn)

在介紹 Provider Fiber 節(jié)點處理前,我們需要先了解下 Consumer 消費組件如何使用 context value,以便于更好理解 Provider 的實現(xiàn)。

useContext 接收 context 對象作為參數(shù),從 context._currentValue 中讀取 value 值。

不過,除了讀取 value 值外,還會將 context 信息保存在當前組件 Fiber.dependencies 上。

目的是為了在 Provider value 發(fā)生更新時,可以查找到消費組件并標記上更新,執(zhí)行組件的重渲染邏輯。

function useContext(Context) {
  // 將 context 記錄在當前 Fiber.dependencies 節(jié)點上,在 Provider 檢測到 value 更新后,會查找消費組件標記更新。
  const contextItem = {
    context: context,
    next: null, // 一個組件可能注冊多個不同的 context
  };
  if (lastContextDependency === null) {
    lastContextDependency = contextItem;
    currentlyRenderingFiber.dependencies = {
      lanes: NoLanes,
      firstContext: contextItem,
      responders: null
    };
  } else {
    // Append a new context item.
    lastContextDependency = lastContextDependency.next = contextItem;
  }
  return context._currentValue;
}

3.4、Context.Provider 在 Fiber 架構下的實現(xiàn)機制

經過上面 useContext 消費組件的分析,我們需要思考兩點:

  • <Provider> 組件上的 value 值何時更新到 context._currentValue ?
  • Provider.value 值發(fā)生更新后,如果能夠讓消費組件進行重渲染 ?

這兩點都會在這里找到答案。

在 example 中,點擊「觸發(fā)更新」div 后,React 會進入調度更新階段。我們通過斷點定位到 Context.Provider Fiber 節(jié)點的 Reconciler/beginWork 之中。

Provider Fiber 類型為 ContextProvider,因此進入 tag switch case 中的 updateContextProvider。

function beginWork(current, workInProgress, renderLanes) {
  ...
  switch (workInProgress.tag) {
    case ContextProvider:
      return updateContextProvider(current, workInProgress, renderLanes);
  }
}

首先,更新 context._currentValue,比較新老 value 是否發(fā)生變化。

注意,這里使用的是 Object.is,通常我們傳遞的 value 都是一個復雜對象類型,它將比較兩個對象的引用地址是否相同。

若引用地址未發(fā)生變化,則會進入 bailout 復用當前 Fiber 節(jié)點。

在 bailout 中,會檢查該 Fiber 的所有子孫 Fiber 是否存在 lane 更新。若所有子孫 Fiber 本次都沒有更新需要執(zhí)行,則 bailout 會直接返回 null,整棵子樹都被跳過更新。

function updateContextProvider(current, workInProgress, renderLanes) {
  var providerType = workInProgress.type;
  var context = providerType._context;
  var newProps = workInProgress.pendingProps;
  var oldProps = workInProgress.memoizedProps;
  var newValue = newProps.value;
  var oldValue = oldProps.value;
  // 1、更新 value prop 到 context 中
  context._currentValue = nextValue;
  // 2、比較前后 value 是否有變化,這里使用 Object.is 進行比較(對于對象,僅比較引用地址是否相同)
  if (objectIs(oldValue, newValue)) {
    // children 也相同,進入 bailout,結束子樹的協(xié)調
    if (oldProps.children === newProps.children && !hasContextChanged()) {
      return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
    }
  } else {
    // 3、context value 發(fā)生變化,深度優(yōu)先遍歷查找 consumer 消費組件,標記更新
    propagateContextChange(workInProgress, context, changedBits, renderLanes);
  }
  // ... reconciler children
}

context.value 發(fā)生變化,調用 propagateContextChange 對 Fiber 子樹向下深度優(yōu)先遍歷,目的是為了查找 Context 消費組件,并為其標記 lane 更新,即讓其后續(xù)進入 Reconciler/beginWork 階段后不滿足 bailout 條件 !includesSomeLane(renderLanes, updateLanes)。

function propagateContextChange(workInProgress, context, changedBits, renderLanes) {
  var fiber = workInProgress.child;
  while (fiber !== null) {
    var nextFiber;
    var list = fiber.dependencies; // 若 fiber 屬于一個 Consumer 組件,dependencies 上記錄了 context 對象
    if (list !== null) {
      var dependency = list.firstContext; // 拿出第一個 context
      while (dependency !== null) {
        // Check if the context matches.
        if (dependency.context === context) {
          if (fiber.tag === ClassComponent) {
            var update = createUpdate(NoTimestamp, pickArbitraryLane(renderLanes));
            update.tag = ForceUpdate;
            enqueueUpdate(fiber, update);
          }
          // 標記組件存在更新,!includesSomeLane(renderLanes, updateLanes) 
          fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
          // 在上層 Fiber 樹的節(jié)點上標記 childLanes 存在更新
          scheduleWorkOnParentPath(fiber.return, renderLanes);
          ...
          break
        }
      }
    }
  }
}

3.5、小結

通常,一個組件的更新可通過執(zhí)行內部 setState 來生成,其方式也是標記 Fiber.lane 讓組件不進入 bailout;

對于 Context,當 Provider.value 發(fā)生更新后,它會查找子樹找到消費組件,為消費組件的 Fiber 節(jié)點標記 lane。

當組件(函數(shù)組件)進入 Reconciler/beginWork 階段進行處理時,不滿足 bailout,就會重新被調用進行重渲染,這時執(zhí)行 useContext,就會拿到最新的 context.__currentValue。

這就是 React.context 實現(xiàn)過程。

四、注意事項

React 性能一大關鍵在于,減少不必要的 render。Context 會通過 Object.is(),即 === 來比較前后 value 是否嚴格相等。這里可能會有一些陷阱:當注冊 Provider 的父組件進行重渲染時,會導致消費組件觸發(fā)意外渲染。

如下例子,當每一次 Provider 重渲染時,以下的代碼會重渲染所有消費組件,因為 value 屬性總是被賦值為新的對象:

class App extends React.Component {
  render() {
    return (
      <MyContext.Provider value={{something: 'something'}}>
        <Toolbar />
      </MyContext.Provider>
    );
  }
}

為了防止這種情況,可以將 value 狀態(tài)提升到父節(jié)點的 state 里:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: { something: 'something' },
    };
  }
  render() {
    return (
      <Provider value={this.state.value}>
        <Toolbar />
      </Provider>
    );
  }
}

五、對比 useSelector

從「注意事項」可以考慮:要想使消費組件進行重渲染,context value 必須返回一個全新對象,這將導致所有消費組件都進行重渲染,這個開銷是非常大的,因為有一些組件所依賴的值可能并未發(fā)生變化。

當然有一種直觀做法是將「狀態(tài)」分離在不同 Context 之中。

react-redux useSelector 則是采用訂閱 redux store.state 更新,去通知消費組件「按需」進行重渲染(比較所依賴的 state 前后是否發(fā)生變化)。

  • 提供給 Context.Provider 的 value 對象地址不會發(fā)生變化,這使得子組件中使用了 useSelector -> useContext,但不會因頂層數(shù)據(jù)而進行重渲染。
  • store.state 數(shù)據(jù)變化組件如何更新呢?react-redux 訂閱了 redux store.state 發(fā)生更新的動作,然后通知組件「按需」執(zhí)行重渲染。

以上就是React Context原理深入理解源碼示例分析的詳細內容,更多關于React Context原理的資料請關注腳本之家其它相關文章!

相關文章

  • React實現(xiàn)雙向綁定示例代碼

    React實現(xiàn)雙向綁定示例代碼

    這篇文章給大家介紹了在React中如何實現(xiàn)雙向綁定,文中給出了示例代碼,對大家的理解與學習很有幫助,有需要的朋友下面來一起看看吧。
    2016-09-09
  • React Native 真機斷點調試+跨域資源加載出錯問題的解決方法

    React Native 真機斷點調試+跨域資源加載出錯問題的解決方法

    下面小編就為大家分享一篇React Native 真機斷點調試+跨域資源加載出錯問題的解決方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-01-01
  • React 父子組件通信的實現(xiàn)方法

    React 父子組件通信的實現(xiàn)方法

    這篇文章主要介紹了React 父子組件通信的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-12-12
  • React 首頁加載慢問題性能優(yōu)化案例詳解

    React 首頁加載慢問題性能優(yōu)化案例詳解

    這篇文章主要介紹了React 首頁加載慢問題性能優(yōu)化案例詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下
    2021-09-09
  • ReactNative Image組件使用詳解

    ReactNative Image組件使用詳解

    本篇文章主要介紹了ReactNative Image組件使用詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-08-08
  • 基于webpack開發(fā)react-cli的詳細步驟

    基于webpack開發(fā)react-cli的詳細步驟

    這篇文章主要介紹了基于webpack開發(fā)react-cli的詳細步驟,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-06-06
  • React配置多個代理實現(xiàn)數(shù)據(jù)請求返回問題

    React配置多個代理實現(xiàn)數(shù)據(jù)請求返回問題

    這篇文章主要介紹了React之配置多個代理實現(xiàn)數(shù)據(jù)請求返回問題,本文通過示例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-08-08
  • React實現(xiàn)一個高度自適應的虛擬列表

    React實現(xiàn)一個高度自適應的虛擬列表

    這篇文章主要介紹了React如何實現(xiàn)一個高度自適應的虛擬列表,幫助大家更好的理解和學習使用React,感興趣的朋友可以了解下
    2021-04-04
  • react-native-video實現(xiàn)視頻全屏播放的方法

    react-native-video實現(xiàn)視頻全屏播放的方法

    這篇文章主要介紹了react-native-video實現(xiàn)視頻全屏播放的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-03-03
  • 為react組件庫添加typescript類型提示的方法

    為react組件庫添加typescript類型提示的方法

    這篇文章主要介紹了為react組件庫添加typescript類型提示,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-06-06

最新評論