React?Context源碼實(shí)現(xiàn)原理詳解
什么是 Context
目前來(lái)看 Context 是一個(gè)非常強(qiáng)大但是很多時(shí)候不會(huì)直接使用的 api。大多數(shù)項(xiàng)目不會(huì)直接使用 createContext 然后向下面?zhèn)鬟f數(shù)據(jù),而是采用第三方庫(kù)(react-redux)。
想想項(xiàng)目中是不是經(jīng)常會(huì)用到 @connect(...)(Comp) 以及 <Provider value={store}><App /></Provider>?
Context 提供了一個(gè)無(wú)需為每層組件手動(dòng)添加 props,就能在組件樹(shù)間進(jìn)行數(shù)據(jù)傳遞的方法。
一個(gè)頂層數(shù)據(jù),想要傳遞到某些深層組件,通過(guò) props 逐層傳遞將會(huì)非常繁瑣,使用 Context 可避免顯式地通過(guò)組件樹(shù)逐層傳遞 props。
Context 使用示例
import React, { Component, createContext, useConText } from 'react'
const ColorContext = createContext(null)
const { Provider, Consumer } = ColorContext
console.log('ColorContext', ColorContext)
console.log('Provider', Provider)
console.log('Consumer', Consumer)
class App extends Component {
constructor(props) {
super(props)
this.state = {
color: 'red',
background: 'cyan',
}
}
render() {
return <Provider value={this.state}>{this.props.children}</Provider>
}
}
function Article({ children }) {
return (
<App>
<h1>Context</h1>
<p>hello world</p>
{children} </App>
)
}
function Paragraph({ color, background }) {
return (
<div style={{ backgroundColor: background }}>
<span style={{ color }}>text</span>
</div>
)
}
function TestContext() {
return (
<Article>
<Consumer>{state => <Paragraph {...state} />}</Consumer>
</Article>
)
}
export default TestContext
頁(yè)面呈現(xiàn)出的效果

打印 ColorContext、Provider、Consumer

createContext
// createContext 可以讓我們實(shí)現(xiàn)狀態(tài)管理
// 還能夠解決傳遞 Props drilling 的問(wèn)題
// 假如一個(gè)子組件需要父組件的一個(gè)屬性,但是中間間隔了好幾層,這就會(huì)出現(xiàn)開(kāi)發(fā)和維護(hù)的一個(gè)成本。這時(shí)候就可以通過(guò)這個(gè) API 來(lái)解決
function createContext(defaultValue, calculateChangedBits) {
var context = {
?typeof: REACT_CONTEXT_TYPE,
_calculateChangedBits: calculateChangedBits,
// As a workaround to support multiple concurrent renderers, we categorize
// some renderers as primary and others as secondary. We only expect
// there to be two concurrent renderers at most: React Native (primary) and
// Fabric (secondary); React DOM (primary) and React ART (secondary).
// Secondary renderers store their context values on separate fields.
// 以下兩個(gè)屬性是為了適配多平臺(tái)
_currentValue: defaultValue,
_currentValue2: defaultValue,
// Used to track how many concurrent renderers this context currently
// supports within in a single renderer. Such as parallel server rendering.
_threadCount: 0,
// These are circular
Provider: null,
Consumer: null
};
// 以下的代碼很簡(jiǎn)單,就是在 context 上掛載 Provider 和 Consumer,讓外部去使用
context.Provider = {
?typeof: REACT_PROVIDER_TYPE,
_context: context
};
var Consumer = {
?typeof: REACT_CONTEXT_TYPE,
_context: context,
_calculateChangedBits: context._calculateChangedBits
};
context.Consumer = Consumer;
context._currentRenderer = null;
context._currentRenderer2 = null;
return context;
}
在 react 包里面僅僅是生成了幾個(gè)對(duì)象,比較簡(jiǎn)單,接下來(lái)看看它發(fā)揮作用的地方。
在 Consumer children 的匿名函數(shù)里面打 debugger。

查看調(diào)用棧

主要是 newChildren = render(newValue);,newChildren 是 Consumer 的 children 被調(diào)用之后的返回值,render 就是 children,newValue 是從 Provider value 屬性的賦值。
newProps

newValue

接下來(lái)看 readContext 的實(shí)現(xiàn)
let lastContextDependency: ContextDependency<mixed> | null = null;
let currentlyRenderingFiber: Fiber | null = null;
// 在 prepareToReadContext 函數(shù)
currentlyRenderingFiber = workInProgress;
export function readContext<T>(
context: ReactContext<T>,
observedBits: void | number | boolean,
): T {
let contextItem = {
context: ((context: any): ReactContext<mixed>),
observedBits: resolvedObservedBits,
next: null,
};
if (lastContextDependency === null) {
// This is the first dependency for this component. Create a new list.
lastContextDependency = contextItem;
currentlyRenderingFiber.contextDependencies = {
first: contextItem,
expirationTime: NoWork,
};
} else {
// Append a new context item.
lastContextDependency = lastContextDependency.next = contextItem;
}
}
// isPrimaryRenderer 為 true,定義的就是 true
// 實(shí)際就是一直會(huì)返回 context._currentValue
return isPrimaryRenderer ? context._currentValue : context._currentValue2;
}
跳過(guò)中間,最后一句 return context._currentValue,而

就把頂層傳下來(lái)的 context 的值取到了
context 為什么從上層可以一直往下面?zhèn)鬟@點(diǎn)現(xiàn)在還沒(méi)有看懂,后面熟悉跨組件傳遞的實(shí)現(xiàn)之后再寫一篇文章解釋,囧。
Context 的設(shè)計(jì)非常特別
Provider Consumer 是 context 的兩個(gè)屬性。
var context = {
?typeof: REACT_CONTEXT_TYPE,
_currentValue: defaultValue,
_currentValue2: defaultValue,
Provider: null,
Consumer: null
};
Provider 的 ?typeof 是 REACT_PROVIDER_TYPE,它帶有一個(gè) _context 屬性,指向的就是 context 本身,也就是自己的兒子有一個(gè)屬性指向自己?。?!
context.Provider = {
?typeof: REACT_PROVIDER_TYPE,
_context: context
};
Consumer 的 ?typeof 是 REACT_CONTEXT_TYPE,它帶也有一個(gè) _context 屬性,也是自己的兒子有一個(gè)屬性指向自己?。。?/p>
var Consumer = {
?typeof: REACT_CONTEXT_TYPE,
_context: context,
_calculateChangedBits: context._calculateChangedBits
};
所以可以做一個(gè)猜想, Provider 的 value 屬性賦予的新值肯定通過(guò) _context 屬性傳到了 context 上,修改了 _currentValue。同樣,Consumer 也是依據(jù) _context 拿到了 context 的 _currentValue,然后 render(newValue) 執(zhí)行 children 函數(shù)。
useContext
useContext 是 react hooks 提供的一個(gè)功能,可以簡(jiǎn)化 context 值得獲取。
下面看使用代碼
import React, { useContext, createContext } from 'react'
const NameCtx = createContext({ name: 'yuny' })
function Title() {
const { name } = useContext(NameCtx)
return <h1># {name}</h1>
}
function App() {
return (
<NameCtx.Provider value={{ name: 'lxfriday' }}>
<Title />
</NameCtx.Provider>
)
}
export default App
我么初始值給的是 {name: 'yuny'},實(shí)際又重新賦值 {name: 'lxfriday'},最終頁(yè)面顯示的是 lxfriday。

useContext 相關(guān)源碼
先看看 react 包中導(dǎo)出的 useContext
/** * useContext * @param Context {ReactContext} createContext 返回的結(jié)果 * @param unstable_observedBits {number | boolean | void} 計(jì)算新老 context 變化相關(guān)的,useContext() second argument is reserved for future * @returns {*} 返回的是 context 的值 */
export function useContext<T>(
Context: ReactContext<T>, unstable_observedBits: number | boolean | void,
) {
const dispatcher = resolveDispatcher();
return dispatcher.useContext(Context, unstable_observedBits);
}
// Invalid hook call. Hooks can only be called inside of the body of a function component.
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
return dispatcher;
}
/** * Keeps track of the current dispatcher. */
const ReactCurrentDispatcher = {
/** * @internal
* @type {ReactComponent} */
current: (null: null | Dispatcher),
};
看看 Dispatcher,都是和 React Hooks 相關(guān)的。

再到 react-reconciler/src/ReactFiberHooks.js 中,有 HooksDispatcherOnMountInDEV 和 HooksDispatcherOnMount,帶 InDEV 的應(yīng)該是在 development 環(huán)境會(huì)使用到的,不帶的是在 `production 會(huì)使用到。
const HooksDispatcherOnMount: Dispatcher = {
readContext,
useCallback: mountCallback,
useContext: readContext,
useEffect: mountEffect,
useImperativeHandle: mountImperativeHandle,
useLayoutEffect: mountLayoutEffect,
useMemo: mountMemo,
useReducer: mountReducer,
useRef: mountRef,
useState: mountState,
useDebugValue: mountDebugValue,
};
HooksDispatcherOnMountInDEV = {
// ...
useContext<T>(
context: ReactContext<T>,
observedBits: void | number | boolean,
): T {
return readContext(context, observedBits);
},
}
在上面 useContext 經(jīng)過(guò) readContext 返回了 context 的值,readContext 在上面有源碼介紹。
debugger 查看調(diào)用棧
初始的 useContext

在 HooksDispatcherOnMountInDEV 中

readContext 中

經(jīng)過(guò)上面源碼的詳細(xì)分析, 大家對(duì) context 的創(chuàng)建和 context 取值應(yīng)該了解了,context 設(shè)計(jì)真的非常妙??!
以上就是React Context源碼實(shí)現(xiàn)原理詳解的詳細(xì)內(nèi)容,更多關(guān)于React Context 源碼實(shí)現(xiàn)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
react-router-dom?v6?使用詳細(xì)示例
這篇文章主要介紹了react-router-dom?v6使用詳細(xì)示例,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下,希望對(duì)你的學(xué)習(xí)有所幫助2022-09-09
從零開(kāi)始搭建一個(gè)react項(xiàng)目開(kāi)發(fā)
這篇文章主要介紹了從零開(kāi)始搭建一個(gè)react項(xiàng)目開(kāi)發(fā),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-02-02
基于React Native 0.52實(shí)現(xiàn)輪播圖效果
這篇文章主要為大家詳細(xì)介紹了基于React Native 0.52實(shí)現(xiàn)輪播圖效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-11-11
react?native?reanimated實(shí)現(xiàn)動(dòng)畫(huà)示例詳解
這篇文章主要為大家介紹了react?native?reanimated實(shí)現(xiàn)動(dòng)畫(huà)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
react-native 配置@符號(hào)絕對(duì)路徑配置和絕對(duì)路徑?jīng)]有提示的問(wèn)題
本文主要介紹了react-native 配置@符號(hào)絕對(duì)路徑配置和絕對(duì)路徑?jīng)]有提示的問(wèn)題,文中通過(guò)圖文示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-01-01
React18?useState何時(shí)執(zhí)行更新及微任務(wù)理解
這篇文章主要為大家介紹了React18?useState何時(shí)執(zhí)行更新及微任務(wù)理解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
react數(shù)據(jù)管理中的setState與Props詳解
setState?是?React?中用于更新組件狀態(tài)(state)的方法,本文給大家介紹react數(shù)據(jù)管理中的setState與Props知識(shí),感興趣的朋友跟隨小編一起看看吧2023-10-10

