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

react數(shù)據(jù)管理機制React.Context源碼解析

 更新時間:2022年11月24日 14:35:02   作者:flyzz177  
這篇文章主要為大家介紹了react數(shù)據(jù)管理機制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è)計目的是為了共享那些對于一個組件樹而言是“全局”的數(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ā)生變化時,它內(nèi)部的所有消費組件都會重新渲染。

注意,當 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 組件內(nèi)使用 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;
}

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

3.2、 JSX 編譯

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

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

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

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

有了對象描述結(jié)構(gòu),接下來進入渲染流程并在 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 架構(gòu)下的實現(xiàn)機制

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

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

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

在 example 中,點擊「觸發(fā)更新」div 后,React 會進入調(diào)度更新階段。我們通過斷點定位到 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,結(jié)束子樹的協(xié)調(diào)
    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ā)生變化,調(diào)用 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、總結(jié)

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

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

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

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

四、注意事項

React 性能一大關(guān)鍵在于,減少不必要的 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數(shù)據(jù)管理機制React.Context源碼解析的詳細內(nèi)容,更多關(guān)于React.Context 數(shù)據(jù)管理的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • React實現(xiàn)點擊刪除列表中對應項

    React實現(xiàn)點擊刪除列表中對應項

    本文主要介紹了React 點擊刪除列表中對應項的方法。具有一定的參考價值,下面跟著小編一起來看下吧
    2017-01-01
  • React useEffect使用教程

    React useEffect使用教程

    useEffect是react v16.8新引入的特性。我們可以把useEffect hook看作是componentDidMount、componentDidUpdate、componentWillUnmounrt三個函數(shù)的組合
    2022-10-10
  • react項目中redux的調(diào)試工具不起作用的解決

    react項目中redux的調(diào)試工具不起作用的解決

    這篇文章主要介紹了react項目中redux的調(diào)試工具不起作用的解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • react使用useState修改對象或者數(shù)組的值無法改變視圖的問題

    react使用useState修改對象或者數(shù)組的值無法改變視圖的問題

    這篇文章主要介紹了react使用useState修改對象或者數(shù)組的值無法改變視圖的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • 淺析React中的受控組件和非受控組件

    淺析React中的受控組件和非受控組件

    具體來說這是一種react非受控組件,其狀態(tài)是在input的react內(nèi)部控制,不受調(diào)用者控制??梢允褂檬芸亟M件來實現(xiàn)。下面就說說這個React中的受控組件與非受控組件的相關(guān)知識,感興趣的朋友一起看看吧
    2021-05-05
  • create-react-app構(gòu)建項目慢的解決方法

    create-react-app構(gòu)建項目慢的解決方法

    這篇文章主要介紹了create-react-app構(gòu)建項目慢的解決方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-03-03
  • React 并發(fā)功能體驗(前端的并發(fā)模式)

    React 并發(fā)功能體驗(前端的并發(fā)模式)

    React 是由 Facebook 軟件工程師 Jordan Walke 創(chuàng)建,React 的第一個版本在七年前問世,現(xiàn)在,F(xiàn)acebook 負責維護,本文給大家介紹React 并發(fā)功能體驗前端并發(fā)模式的問題,感興趣的朋友跟隨小編一起看看吧
    2021-07-07
  • 使用React?Hooks模擬生命周期的實現(xiàn)方法

    使用React?Hooks模擬生命周期的實現(xiàn)方法

    這篇文章主要介紹了使用React?Hooks模擬生命周期,本文舉例說明如何使用 hooks 來模擬比較常見的 class 組件生命周期,需要的朋友可以參考下
    2023-02-02
  • React Diff原理深入分析

    React Diff原理深入分析

    這篇文章主要介紹了React Diff原理的相關(guān)資料,幫助大家更好的理解和學習使用React框架,感興趣的朋友可以了解下
    2021-04-04
  • React?程序設(shè)計簡單的輕量級自動完成搜索框應用

    React?程序設(shè)計簡單的輕量級自動完成搜索框應用

    這篇文章主要為大家介紹了React?程序設(shè)計簡單的輕量級自動完成搜索框應用,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-10-10

最新評論