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)之后再寫(xiě)一篇文章解釋?zhuān)瑖濉?/p>
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è)屬性指向自己?。?!
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ì)真的非常妙?。?/p>
以上就是React Context源碼實(shí)現(xiàn)原理詳解的詳細(xì)內(nèi)容,更多關(guān)于React Context 源碼實(shí)現(xiàn)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
React render核心階段深入探究穿插scheduler與reconciler
這篇文章主要介紹了React render核心階段穿插scheduler與reconciler,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2022-11-11基于React實(shí)現(xiàn)一個(gè)todo打勾效果
這篇文章主要為大家詳細(xì)介紹了如何基于React實(shí)現(xiàn)一個(gè)todo打勾效果,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03react拖拽react-beautiful-dnd一維數(shù)組二維數(shù)組拖拽功能
二維數(shù)組可以拖拽,但是不可以編輯+拖拽,如果想要實(shí)現(xiàn)編輯+拖拽,還是需要轉(zhuǎn)換成一維數(shù)組,本文給大家介紹react拖拽react-beautiful-dnd的相關(guān)知識(shí),感興趣的朋友跟隨小編一起看看吧2024-03-03React?createRef循環(huán)動(dòng)態(tài)賦值ref問(wèn)題
這篇文章主要介紹了React?createRef循環(huán)動(dòng)態(tài)賦值ref問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01React Native第三方平臺(tái)分享的實(shí)例(Android,IOS雙平臺(tái))
本篇文章主要介紹了React Native第三方平臺(tái)分享的實(shí)例(Android,IOS雙平臺(tái)),具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08