av激情亚洲男人的天堂国语,日韩欧美精品一中文字幕,无码av一区二区三区无码,国产又色又爽又刺激的a片,国产又色又爽又刺激的a片

看完ReactConext源碼,就知道怎么繞過Provider修改它了

context 是 React 提供的特性,可以實(shí)現(xiàn)任意層級(jí)組件之間的數(shù)據(jù)傳遞。

可能大家用過 context,但是不知道它是怎么實(shí)現(xiàn)的。

本文就從源碼層面來講下 cotnext 的原理,而且我們能從中發(fā)現(xiàn)一些 hack 的小技巧。

首先,我們先過一下 context 的使用方式:

context 的使用

有這樣的 3個(gè)組件,One、Two、Three:

我們想不通過 props 從 One 傳遞數(shù)據(jù)到 Three,這時(shí)候就可以用 context 了:

調(diào)用 createContext 來創(chuàng)建 context 對(duì)象,初始化數(shù)據(jù)是 'dong'。

Three 組件里就可以通過 useContext 把 context 數(shù)據(jù)取出來了:

除了初始化的時(shí)候可以傳入,后面也可以通過 Provider 來修改 context 數(shù)據(jù):

我們通過 Provider 傳入了新的 value,覆蓋了初始值,這樣 useContext 拿到的值就變了:

函數(shù)組件是用 useContext 的 hook 來取,class 組件則是用 Consumer 來取:

我們依然是通過 createContext 創(chuàng)建 context 對(duì)象,通過 Provider 修改了 value。

現(xiàn)在 Three 變成了 class 組件,所以使用 context 的方式要通過 Consumer,它的 children 部分傳入 render 函數(shù),參數(shù)里就能拿到 context 數(shù)據(jù):

這分別是 class 組件和 function 組件使用 context 的方式,我們小結(jié)一下:

context 使用 React.createContext 創(chuàng)建,可以傳入初始值,后面也可以通過 Provider 來修改其中的值,使用 context 值的時(shí)候,如果是 function 組件,可以通過 useContext 的 hook 來取,而 class 組件是用 Consumer 傳入一個(gè) render 函數(shù)的方式來取。

學(xué)會(huì)了 context 怎么用,我們?cè)賮砜聪滤膶?shí)現(xiàn)原理:

context 的實(shí)現(xiàn)

首先我們看下 createContext 的源碼:

它創(chuàng)建了一個(gè) context 對(duì)象,有 _currentValue 屬性,一看就是保存值的,還有 Consumer 和 Provider 兩個(gè)屬性。

Consumer 和 Provider 都是通過 _context 保存了 context 對(duì)象的引用。

并且它們都有 $$typeof 來標(biāo)識(shí)類型。

這就是 context 對(duì)象的結(jié)構(gòu):

那這個(gè) context 對(duì)象是怎么結(jié)合到 React 渲染流程里的呢?

就是通過 jsx 結(jié)合的呀:

jsx 編譯以后會(huì)產(chǎn)生 render function。

比如上面那段 jsx 編譯后是這樣的:

新版 React 不調(diào)用 React.createElement 了,而是 jsx 函數(shù),也就是上面的 jsxDev。

看這第一個(gè)參數(shù)是啥,有 $$typeof 和 _context 屬性,不就是我們傳入的 context.Provider 么:

value 是在 props 參數(shù)里傳入的:

jsx 執(zhí)行會(huì)產(chǎn)生一個(gè)個(gè) vdom:

這樣 context 就保存到了 vdom 節(jié)點(diǎn)上。

那遞歸渲染的時(shí)候不就能從 vdom 拿到 context 了么?

別著急,React 現(xiàn)在不是 vdom 直接渲染了,而是會(huì)先把 vdom 轉(zhuǎn)成 fiber,這個(gè)過程叫做 reconcile:

fiber 的結(jié)構(gòu)中保存了兄弟節(jié)點(diǎn)和父節(jié)點(diǎn)的引用,這是 vdom 所沒有的,vdom 只有子節(jié)點(diǎn)的引用,所以 vdom 變成 fiber 以后就變成了可打斷的,因?yàn)榫退銛嗔艘材苷业叫值芄?jié)點(diǎn)和父節(jié)點(diǎn)繼續(xù)處理。

那保存在 vdom 中的 context 不就自然的轉(zhuǎn)移到了 fiber 節(jié)點(diǎn)上了么?

創(chuàng)建 fiber 的代碼是這樣的:

調(diào)用 createFiber 最終會(huì) new FiberNode

然后也是創(chuàng)建一個(gè)對(duì)象:

這個(gè)和 vdom 差別不大,只不過屬性不一樣了。

你看這里的 fiber.type,不就是保存在 vdom 上的 context.Provider 對(duì)象么?

那之后這個(gè) fiber 節(jié)點(diǎn)是怎么處理的呢?

你會(huì)發(fā)現(xiàn)在處理 fiber 節(jié)點(diǎn)的時(shí)候,會(huì)判斷 fiber.tag,不同的類型做不同的處理:

FunctionComponent 和 ClassComponent 的 fiber 節(jié)點(diǎn)的處理就不同。

下面可以找到 ContextProvider 的:

它的實(shí)現(xiàn)就是修改了 context 中的值:

pushProvider 就是最終修改 context 值的地方:

通過 _context 拿到了 Provider 所引用的 context 對(duì)象,然后修改它的 _currentValue 屬性,也就是 context 中的值。

對(duì)照著這個(gè) context 對(duì)象的結(jié)構(gòu)一看就明白了:

同理,后面處理到 Consumer 的時(shí)候也是這樣拿到 context 的:

ContextConsumer 的 fiber 節(jié)點(diǎn)也會(huì)做專門的處理:

workInProgress 就是當(dāng)前 fiber 節(jié)點(diǎn),它的 type 保存了 context.Consumer 對(duì)象。

我們通過 readContext 拿到其中的值,也就是取 _currentValue 屬性。

拿到最新的 context 中的值后就觸發(fā)子組件的渲染:

所以說為什么 Consumer 必須要傳入一個(gè) render 函數(shù)作為子節(jié)點(diǎn)不就清楚了么:

這樣我們就取到了 context 中的值,并觸發(fā)了子組件的渲染。

使用 context 的方式還有 useContext 的 hook,其實(shí)那個(gè)也是一樣的:

useContext 也是調(diào)用了 readContext 來讀取了 context 的 _currentValue 屬性:

當(dāng)然,useContext 的 context 不是從 fiber.type 來取的,而是用的傳入的 context:

但是都是引用的同一個(gè)對(duì)象,在 Provider 里修改了 context 的 value,這里取到的 context 同樣也是新的。

我們小結(jié)下 context 的實(shí)現(xiàn)原理:

createContext 會(huì)創(chuàng)建 context 對(duì)象,它有 _currentValue 保存值,還引用了 Provider 和 Consumer 兩個(gè)對(duì)象,Provider 和 Consumer 里有 _context 屬性引用了 context。

在 jsx 渲染的時(shí)候會(huì)把 Provider 和 Consumer 對(duì)象保存到 vdom 上,后面 reconcile 的時(shí)候會(huì)轉(zhuǎn)移到 fiber 的 type 屬性上,處理 fiber 節(jié)點(diǎn)的時(shí)候會(huì)根據(jù)類型做不同的處理:

如果是 Provider,就會(huì)根據(jù)傳入的 value 修改 context 的值,也就是 _currentValue 屬性

如果是 Consumer,則會(huì)讀取 context 的值然后觸發(fā)子組件的渲染。

函數(shù)組件會(huì)使用 useContext 的 hook,最終也是讀取了同一個(gè) context 的 _currentValue 的 值

理清了 context 的實(shí)現(xiàn)原理,我們是不是能發(fā)現(xiàn)一些 hack 的技巧呢?

比如 Provider 其實(shí)就是修改 _currentValue 的,那我們自己修改 context._currentValue 不就不用 Provider 了?

試一下:

用 Provider 是這樣的:

其實(shí)直接修改 _currentValue 也可以:

但是不推薦這樣寫,因?yàn)檫@是個(gè)私有屬性,萬一哪一天變了呢?

總結(jié)

context 是 React 提供的任意層級(jí)組件之間通信的機(jī)制,我們先過了一遍它的使用方式:

通過 createContext 來創(chuàng)建 context 對(duì)象,可以傳入初始值,可以通過 jsx 里的 context.Provider 來修改 context 值,通過 context.Consumer 來拿到 context 的值,如果是函數(shù)組件,是通過 useContext 的 hook 來取。

然后通過源碼理清了 context 的實(shí)現(xiàn)原理:

jsx 里的 Provider 和 Consumer 對(duì)象會(huì)被保存到 vdom 中,最后會(huì)轉(zhuǎn)移到 fiber 節(jié)點(diǎn)的 type 屬性上,fiber 處理的時(shí)候,對(duì) Provider 會(huì)修改 context 的 value,而 Consumer 則會(huì)取出 context 的值然后觸發(fā)子組件渲染。函數(shù)組件的 useContext 的 hook 也是從同一個(gè) context 對(duì)象讀取的數(shù)據(jù)。

然后我們發(fā)現(xiàn)了繞過 Provider 修改 context 的方式,就是直接修改 _currentValue,但是不推薦這樣做,因?yàn)樗接袑傩圆灰欢ㄉ稌r(shí)候就變了。

context 是這樣實(shí)現(xiàn)的,其他特性的實(shí)現(xiàn)原理也大同小異,只不過是掛到了不同的屬性上,處理 fiber 的時(shí)候做了分成了不同的類型來處理。

理清 context 的原理之后,你是否對(duì) vdom、fiber,還有 reconcile 的過程都有更深的理解了呢?


當(dāng)前題目:看完ReactConext源碼,就知道怎么繞過Provider修改它了
文章出自:http://uogjgqi.cn/article/ccsjsge.html
掃二維碼與項(xiàng)目經(jīng)理溝通

我們?cè)谖⑿派?4小時(shí)期待你的聲音

解答本文疑問/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流