自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

React Context的核心實現(xiàn),就五行代碼

開發(fā) 前端
React Context的實現(xiàn)就是個典型例子,當(dāng)剔除無關(guān)功能的干擾后,他的核心實現(xiàn),僅需「5行代碼」。本文就讓我們看看React Context的核心實現(xiàn)。

大家好,我卡頌。

很多項目的源碼非常復(fù)雜,讓人望而卻步。但在打退堂鼓前,我們應(yīng)該思考一個問題:源碼為什么復(fù)雜?

造成源碼復(fù)雜的原因不外乎有三個:

  1. 功能本身復(fù)雜,造成代碼復(fù)雜。
  2. 編寫者功力不行,寫的代碼復(fù)雜。
  3. 功能本身不復(fù)雜,但同一個模塊耦合了太多功能,看起來復(fù)雜。

如果是原因3,那實際理解起來其實并不難。我們需要的只是有人能幫我們剔除無關(guān)功能的干擾。

React Context的實現(xiàn)就是個典型例子,當(dāng)剔除無關(guān)功能的干擾后,他的核心實現(xiàn),僅需「5行代碼」。

本文就讓我們看看React Context的核心實現(xiàn)。

簡化模型

Context的完整工作流程包括3步:

  1. 定義context
  2. 賦值context
  3. 消費context

以下面的代碼舉例:

const ctx = createContext(null);

function App() {
 return (
  <ctx.Provider value={1}>
   <Cpn />
  </ctx.Provider>
 );
}

function Cpn() {
 const num = useContext(ctx);
 return <div>{num}</div>;
}

其中:

  • const ctx = createContext(null) 用于定義。
  • <ctx.Provider value={1}> 用于賦值。
  • const num = useContext(ctx) 用于消費。

Context數(shù)據(jù)結(jié)構(gòu)(即createContext方法的返回值)也很簡單:

function createContext(defaultValue) {
  const context = {
    $$typeof: REACT_CONTEXT_TYPE,
    Provider: null,
    _currentValue: defaultValue
  };

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

其中context._currentValue保存context當(dāng)前值。

context工作流程的三個步驟其實可以概括為:

  1. 實例化context,并將默認(rèn)值defaultValue賦值給context._currentValue。
  2. 每遇到一個同類型context.Provier,將value賦值給context._currentValue。
  3. useContext(context)就是簡單的取context._currentValue的值就行。

了解了工作流程后我們會發(fā)現(xiàn),Context的核心實現(xiàn)其實就是步驟2。

核心實現(xiàn)

核心實現(xiàn)需要考慮什么呢?還是以上面的示例為例,當(dāng)前只有一層<ctx.Provider>包裹<Cpn />:

function App() {
 return (
  <ctx.Provider value={1}>
   <Cpn />
  </ctx.Provider>
 );
}

在實際項目中,消費ctx的組件(示例中的<Cpn/>)可能被多級<ctx.Provider>包裹,比如:

const ctx = createContext(0);

function App() {
 return (
    <ctx.Provider value={1}>
      <ctx.Provider value={2}>
        <ctx.Provider value={3}>
          <Cpn />
        </ctx.Provider>
        <Cpn />
      </ctx.Provider>
      <Cpn />
    </ctx.Provider>
  );
}

在上面代碼中,ctx的值會從0(默認(rèn)值)逐級變?yōu)?,再從3逐級變?yōu)?,所以沿途消費ctx的<Cpn />組件取得的值分別為:3、2、1。

整個流程就像「操作一個?!?,1、2、3分別入棧,3、2、1分別出棧,過程中棧頂?shù)闹稻褪莄ontext當(dāng)前的值。

基于此,context的核心邏輯包括兩個函數(shù):

function pushProvider(context, newValue) {
 // ...
}

function popProvider(context) {
 // ...
}

其中:

  • 進入ctx.Provider時,執(zhí)行pushProvider方法,類比入棧操作。
  • 離開ctx.Provider時,執(zhí)行popProvider方法,類比出棧操作。

每次執(zhí)行pushProvider時將context._currentValue更新為當(dāng)前值:

function pushProvider(context, newValue) {
 context._currentValue = newValue;
}

同理,popProvider執(zhí)行時將context._currentValue更新為上一個context._currentValue:

function popProvider(context) {
 context._currentValue = /* 上一個context value */
}

該如何表示上一個值呢?我們可以增加一個全局變量prevContextValue,用于保存「上一個同類型的context._currentValue」:

let prevContextValue = null;

function pushProvider(context, newValue) {
 // 保存上一個同類型context value
  prevContextValue = context._currentValue;
  context._currentValue = newValue;
}

function popProvider(context) {
  context._currentValue = prevContextValue;
}

在pushProvider中,執(zhí)行如下語句前:

context._currentValue = newValue;

context._currentValue中保存的就是「上一個同類型的context._currentValue」,將其賦值給prevContextValue。

以下面代碼舉例:

const ctx = createContext(0);

function App() {
 return (
  <ctx.Provider value={1}>
   <Cpn />
  </ctx.Provider>
 );
}

進入ctx.Provider時:

  • prevContextValue賦值為0(context實例化時傳遞的默認(rèn)值)。
  • context._currentValue賦值為1(當(dāng)前值)。

當(dāng)<Cpn />消費ctx時,取得的值就是1。

離開ctx.Provider時:

  • context._currentValue賦值為0(prevContextValue對應(yīng)值)。

但是,我們當(dāng)前的實現(xiàn)只能應(yīng)對一層ctx.Provider,如果是多層ctx.Provider嵌套,我們不知道沿途ctx.Provider對應(yīng)的prevContextValue。

所以,我們可以增加一個棧,用于保存沿途所有ctx.Provider對應(yīng)的prevContextValue:

const prevContextValueStack = [];
let prevContextValue = null;

function pushProvider(context, newValue) {
 prevContextValueStack.push(prevContextValue);
  
 prevContextValue = context._currentValue;
 context._currentValue = newValue;
}

function popProvider(context) {
 context._currentValue = prevContextValue;
 prevContextValue = prevContextValueStack.pop();
}

其中:

  • 執(zhí)行pushProvider時,讓prevContextValue入棧。
  • 執(zhí)行popProvider時,讓prevContextValue出棧。

至此,完成了React Context的核心邏輯,其中pushProvider三行代碼,popProvider兩行代碼。

兩個有意思的點

關(guān)于Context的實現(xiàn),有兩個有意思的點。

第一個點:這個實現(xiàn)太過簡潔(核心就5行代碼),以至于讓人嚴(yán)重懷疑是不是有bug?

比如,全局變量prevContextValue用于保存「上一個同類型的context._currentValue」,如果我們把不同context嵌套使用時會不會有問題?

在下面代碼中,ctxA與ctxB嵌套出現(xiàn):

const ctxA = createContext('default A');
const ctxB = createContext('default B');

function App() {
  return (
    <ctxA.Provider value={'A0'}>
      <ctxB.Provider value={'B0'}>
        <ctxA.Provider value={'A1'}>
          <Cpn />
        </ctxA.Provider>
      </ctxB.Provider>
      <Cpn />
    </ctxA.Provider>
  );
}

當(dāng)離開最內(nèi)層ctxA.Provider時,ctxA._currentValue應(yīng)該從'A1'變?yōu)?A0'??紤]到prevContextValue變量的唯一性以及棧的特性,ctxA._currentValue會不會錯誤的變?yōu)?B0'?

答案是:不會。

JSX結(jié)構(gòu)的確定意味著以下兩點是確定的:

  1. ctx.Provider的進入與離開順序。
  2. 多個ctx.Provider之間嵌套的順序。

第一點保證了當(dāng)進入與離開同一個ctx.Provider時,prevContextValue的值始終與該ctx相關(guān)。

第二點保證了不同ctx.Provider的prevContextValue被以正確的順序入棧、出棧。

第二個有意思的點:我們知道,Hook的使用有個限制 —— 不能在條件語句中使用hook。

究其原因,對于同一個函數(shù)組件,Hook的數(shù)據(jù)保存在一條鏈表上,所以必須保證遍歷鏈表時,鏈表數(shù)據(jù)與Hook一一對應(yīng)。

但我們發(fā)現(xiàn),useContext獲取的其實并不是鏈表數(shù)據(jù),而是ctx._currentValue,這意味著useContext其實是不受這個限制影響的。

總結(jié)

以上五行代碼便是React Context的核心實現(xiàn)。在實際的React源碼中,Context相關(guān)代碼遠(yuǎn)不止五行,這是因為他與其他特性耦合在一塊,比如:

  • 性能優(yōu)化相關(guān)代碼
  • SSR相關(guān)代碼

所以,當(dāng)我們面對復(fù)雜代碼時,不要輕言放棄。仔細(xì)分析下,沒準(zhǔn)兒核心代碼只有幾行呢?

責(zé)任編輯:姜華 來源: 魔術(shù)師卡頌
相關(guān)推薦

2021-12-16 06:21:16

React組件前端

2022-04-15 08:07:21

ReactDiff算法

2016-09-26 17:06:29

2022-05-11 07:41:31

Python驗證碼

2022-02-08 12:30:30

React事件系統(tǒng)React事件系統(tǒng)

2024-12-13 13:58:53

2010-04-19 11:21:32

天御五行可信行為管理

2019-09-04 15:07:15

代碼開發(fā)開源

2014-03-26 09:04:42

算法Floyd最短算法

2020-12-20 10:02:17

ContextReactrender

2023-11-01 11:52:22

lunar語言版本

2023-06-16 09:08:39

ReactContextRFC

2017-03-28 21:03:35

代碼React.js

2022-08-14 23:04:54

React前端框架

2019-11-15 15:50:41

JS代碼React前端

2024-08-01 08:45:17

2010-04-19 10:56:30

天御五行安全服務(wù)中國電信

2017-01-03 15:38:08

Android

2021-12-26 12:10:21

React組件前端

2020-10-12 10:06:26

技術(shù)React代數(shù)
點贊
收藏

51CTO技術(shù)棧公眾號