React Context源码是怎么实现的呢
创始人
2024-02-23 07:27:05
0

目前来看 Context 是一个非常强大但是很多时候不会直接使用的 api。大多数项目不会直接使用 createContext 然后向下面传递数据,而是采用第三方库(react-redux)。

想想项目中是不是经常会用到 @connect(...)(Comp) 以及

什么是 Context

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。

一个顶层数据,想要传递到某些深层组件,通过 props 逐层传递将会非常繁琐,使用 Context 可避免显式地通过组件树逐层传递 props

Context 使用示例

import React, { Component, createContext, useConText } from 'react'
const ColorContext = createContext(null)
const { Provider, Consumer } = ColorContextconsole.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 this.state}>{this.props.children}}
}
function Article({ children }) {return (

Context

hello world

{children}
) } function Paragraph({ color, background }) {return (
{ backgroundColor: background }}>{ color }}>text
) } function TestContext() {return (
{state => ...state} />}
) }export default TestContext

页面呈现出的效果

打印 ColorContextProviderConsumer

createContext

// createContext 可以让我们实现状态管理
// 还能够解决传递 Props drilling 的问题
// 假如一个子组件需要父组件的一个属性,但是中间间隔了好几层,这就会出现开发和维护的一个成本。这时候就可以通过这个 API 来解决
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.// 以下两个属性是为了适配多平台_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 circularProvider: null,Consumer: null};// 以下的代码很简单,就是在 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 包里面仅仅是生成了几个对象,比较简单,接下来看看它发挥作用的地方。

Consumer children 的匿名函数里面打 debugger。

相关参考视频讲解:进入学习

查看调用栈

主要是 newChildren = render(newValue);newChildrenConsumerchildren 被调用之后的返回值,render 就是 childrennewValue 是从 Provider value 属性的赋值。

newProps

newValue

接下来看 readContext 的实现

let lastContextDependency: ContextDependency | null = null;
let currentlyRenderingFiber: Fiber | null = null;
// 在 prepareToReadContext 函数
currentlyRenderingFiber = workInProgress;export function readContext(context: ReactContext,observedBits: void | number | boolean,
): T {let contextItem = {context: ((context: any): ReactContext),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// 实际就是一直会返回  context._currentValuereturn isPrimaryRenderer ? context._currentValue : context._currentValue2;
}

跳过中间,最后一句 return context._currentValue,而

就把顶层传下来的 context 的值取到了

context 为什么从上层可以一直往下面传这点现在还没有看懂,后面熟悉跨组件传递的实现之后再写一篇文章解释,囧。

Context 的设计非常特别

Provider Consumer 是 context 的两个属性。

  var context = {?typeof: REACT_CONTEXT_TYPE,_currentValue: defaultValue,_currentValue2: defaultValue,Provider: null,Consumer: null};

Provider?typeofREACT_PROVIDER_TYPE,它带有一个 _context 属性,指向的就是 context 本身,也就是自己的儿子有一个属性指向自己!!!

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

Consumer?typeofREACT_CONTEXT_TYPE,它带也有一个 _context 属性,也是自己的儿子有一个属性指向自己!!!

  var Consumer = {?typeof: REACT_CONTEXT_TYPE,_context: context,_calculateChangedBits: context._calculateChangedBits};

所以可以做一个猜想, Provider 的 value 属性赋予的新值肯定通过 _context 属性传到了 context 上,修改了 _currentValue。同样,Consumer 也是依据 _context 拿到了 context_currentValue,然后 render(newValue) 执行 children 函数。

useContext

useContext 是 react hooks 提供的一个功能,可以简化 context 值得获取。

下面看使用代码

import React, { useContext, createContext } from 'react'
const NameCtx = createContext({ name: 'yuny' })
function Title() {const { name } = useContext(NameCtx)return 

# {name}

} function App() {return ({ name: 'lxfriday' }}></NameCtx.Provider>) } export default App </code></pre> <p>我么初始值给的是 <code>{name: 'yuny'}</code>,实际又重新赋值 <code>{name: 'lxfriday'}</code>,最终页面显示的是 <code>lxfriday</code>。</p> <p><img src="https://img.pic99.top/cnyincai/202402/d52ffcb2afbe781.png" alt="" /></p> <h3>useContext 相关源码</h3> <p>先看看 <strong>react</strong> 包中导出的 <code>useContext</code></p> <pre><code class="prism language-javascript">/** * useContext * @param Context {ReactContext} createContext 返回的结果 * @param unstable_observedBits {number | boolean | void} 计算新老 context 变化相关的,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); } </code></pre> <pre><code class="prism language-csharp">// Invalid hook call. Hooks can only be called inside of the body of a function component. function resolveDispatcher() {<!-- -->const dispatcher = ReactCurrentDispatcher.current;return dispatcher; } </code></pre> <pre><code class="prism language-javascript">/** * Keeps track of the current dispatcher. */ const ReactCurrentDispatcher = {<!-- -->/** * @internal* @type {ReactComponent} */current: (null: null | Dispatcher), }; </code></pre> <p>看看 <code>Dispatcher</code>,都是和 React Hooks 相关的。</p> <p><img src="https://img.pic99.top/cnyincai/202402/e841487257fe63c.png" alt="" /></p> <p>再到 <strong>react-reconciler/src/ReactFiberHooks.js</strong> 中,有 <code>HooksDispatcherOnMountInDEV</code> 和 <code>HooksDispatcherOnMount</code>,带 <code>InDEV</code> 的应该是在 <code>development</code> 环境会使用到的,不带的是在 `production 会使用到。</p> <pre><code class="prism language-yaml">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);}, } </code></pre> <p>在上面 <code>useContext</code> 经过 <code>readContext</code> 返回了 context 的值,<code>readContext</code> 在上面有源码介绍。</p> <h3>debugger 查看调用栈</h3> <p>初始的 <code>useContext</code></p> <p><img src="https://img.pic99.top/cnyincai/202402/d68cd0b19602ad9.png" alt="" /></p> <p>在 <code>HooksDispatcherOnMountInDEV</code> 中</p> <p><img src="https://img.pic99.top/cnyincai/202402/5f2d5b3e4a4ed3f.png" alt="" /></p> <p><code>readContext</code> 中</p> <p><img src="https://img.pic99.top/cnyincai/202402/6a784255ea3da7.png" alt="" /></p> <p>经过上面源码的详细分析, 大家对 context 的创建和 context 取值应该了解了,context 设计真的非常妙!!</p><link href="https://csdnimg.cn/release/blogv2/dist/mdeditor/css/editerView/markdown_views-22a2fefd3b.css" rel="stylesheet"><link href="https://csdnimg.cn/release/blogv2/dist/mdeditor/css/style-4f8fbf9108.css" rel="stylesheet"> <!--end::Text--> </div> <!--end::Description--> <div class="mt-5"> <!--关键词搜索--> </div> <div class="mt-5"> <p class="fc-show-prev-next"> <strong>上一篇:</strong><a href="/news/20358.html">1. Vue 3.0介绍</a><br> </p> <p class="fc-show-prev-next"> <strong>下一篇:</strong><a href="/news/20362.html">江南中的莲叶何田田是什么意思(江南荷采莲,莲叶何田田的意思) 江南何田田中的江南是什么意思 江南可采莲里的何田田是什么意思</a> </p> </div> <!--begin::Block--> <div class="d-flex flex-stack mb-2 mt-10"> <!--begin::Title--> <h3 class="text-dark fs-5 fw-bold text-gray-800">相关内容</h3> <!--end::Title--> </div> <div class="separator separator-dashed mb-9"></div> <!--end::Block--> <div class="row g-10"> </div> </div> <!--end::Table widget 14--> </div> <!--end::Col--> <!--begin::Col--> <div class="col-xl-4 mt-0"> <!--begin::Chart Widget 35--> <div class="card card-flush h-md-100"> <!--begin::Header--> <div class="card-header pt-5 "> <!--begin::Title--> <h3 class="card-title align-items-start flex-column"> <!--begin::Statistics--> <div class="d-flex align-items-center mb-2"> <!--begin::Currency--> <span class="fs-5 fw-bold text-gray-800 ">热门资讯</span> <!--end::Currency--> </div> <!--end::Statistics--> </h3> <!--end::Title--> </div> <!--end::Header--> <!--begin::Body--> <div class="card-body pt-3"> <!--begin::Item--> <div class="d-flex flex-stack mb-7"> <!--begin::Symbol--> <div class="symbol symbol-60px symbol-2by3 me-4"> <div class="symbol-label" style="background-image: url('/static/assets/images/nopic.gif')"></div> </div> <!--end::Symbol--> <!--begin::Title--> <div class="m-0"> <a href="/news/9408.html" class="text-dark fw-bold text-hover-primary fs-6">喜欢穿一身黑的男生性格(喜欢穿...</a> <span class="text-gray-600 fw-semibold d-block pt-1 fs-7">今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...</span> </div> <!--end::Title--> </div> <!--begin::Item--> <div class="d-flex flex-stack mb-7"> <!--begin::Symbol--> <div class="symbol symbol-60px symbol-2by3 me-4"> <div class="symbol-label" style="background-image: url('/static/assets/images/nopic.gif')"></div> </div> <!--end::Symbol--> <!--begin::Title--> <div class="m-0"> <a href="/news/19462.html" class="text-dark fw-bold text-hover-primary fs-6">发春是什么意思(思春和发春是什...</a> <span class="text-gray-600 fw-semibold d-block pt-1 fs-7">本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...</span> </div> <!--end::Title--> </div> <!--begin::Item--> <div class="d-flex flex-stack mb-7"> <!--begin::Symbol--> <div class="symbol symbol-60px symbol-2by3 me-4"> <div class="symbol-label" style="background-image: url('/static/assets/images/nopic.gif')"></div> </div> <!--end::Symbol--> <!--begin::Title--> <div class="m-0"> <a href="/news/19792.html" class="text-dark fw-bold text-hover-primary fs-6">网络用语zl是什么意思(zl是...</a> <span class="text-gray-600 fw-semibold d-block pt-1 fs-7">今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...</span> </div> <!--end::Title--> </div> <!--begin::Item--> <div class="d-flex flex-stack mb-7"> <!--begin::Symbol--> <div class="symbol symbol-60px symbol-2by3 me-4"> <div class="symbol-label" style="background-image: url('/static/assets/images/nopic.gif')"></div> </div> <!--end::Symbol--> <!--begin::Title--> <div class="m-0"> <a href="/news/9414.html" class="text-dark fw-bold text-hover-primary fs-6">为什么酷狗音乐自己唱的歌不能下...</a> <span class="text-gray-600 fw-semibold d-block pt-1 fs-7">本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...</span> </div> <!--end::Title--> </div> <!--begin::Item--> <div class="d-flex flex-stack mb-7"> <!--begin::Symbol--> <div class="symbol symbol-60px symbol-2by3 me-4"> <div class="symbol-label" style="background-image: url('/static/assets/images/nopic.gif')"></div> </div> <!--end::Symbol--> <!--begin::Title--> <div class="m-0"> <a href="/news/9407.html" class="text-dark fw-bold text-hover-primary fs-6">家里可以做假山养金鱼吗(假山能...</a> <span class="text-gray-600 fw-semibold d-block pt-1 fs-7">今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...</span> </div> <!--end::Title--> </div> <!--begin::Item--> <div class="d-flex flex-stack mb-7"> <!--begin::Symbol--> <div class="symbol symbol-60px symbol-2by3 me-4"> <div class="symbol-label" style="background-image: url('/static/assets/images/nopic.gif')"></div> </div> <!--end::Symbol--> <!--begin::Title--> <div class="m-0"> <a href="/news/9655.html" class="text-dark fw-bold text-hover-primary fs-6">华为下载未安装的文件去哪找(华...</a> <span class="text-gray-600 fw-semibold d-block pt-1 fs-7">今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...</span> </div> <!--end::Title--> </div> <!--begin::Item--> <div class="d-flex flex-stack mb-7"> <!--begin::Symbol--> <div class="symbol symbol-60px symbol-2by3 me-4"> <div class="symbol-label" style="background-image: url('/static/assets/images/nopic.gif')"></div> </div> <!--end::Symbol--> <!--begin::Title--> <div class="m-0"> <a href="/news/9682.html" class="text-dark fw-bold text-hover-primary fs-6">四分五裂是什么生肖什么动物(四...</a> <span class="text-gray-600 fw-semibold d-block pt-1 fs-7">本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...</span> </div> <!--end::Title--> </div> <!--begin::Item--> <div class="d-flex flex-stack mb-7"> <!--begin::Symbol--> <div class="symbol symbol-60px symbol-2by3 me-4"> <div class="symbol-label" style="background-image: url('/static/assets/images/nopic.gif')"></div> </div> <!--end::Symbol--> <!--begin::Title--> <div class="m-0"> <a href="/news/9664.html" class="text-dark fw-bold text-hover-primary fs-6">怎么往应用助手里添加应用(应用...</a> <span class="text-gray-600 fw-semibold d-block pt-1 fs-7">今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...</span> </div> <!--end::Title--> </div> <!--begin::Item--> <div class="d-flex flex-stack mb-7"> <!--begin::Symbol--> <div class="symbol symbol-60px symbol-2by3 me-4"> <div class="symbol-label" style="background-image: url('/static/assets/images/nopic.gif')"></div> </div> <!--end::Symbol--> <!--begin::Title--> <div class="m-0"> <a href="/news/9704.html" class="text-dark fw-bold text-hover-primary fs-6">苏州离哪个飞机场近(苏州离哪个...</a> <span class="text-gray-600 fw-semibold d-block pt-1 fs-7">本篇文章极速百科小编给大家谈谈苏州离哪个飞机场近,以及苏州离哪个飞机场近点对应的知识点,希望对各位有...</span> </div> <!--end::Title--> </div> <!--begin::Item--> <div class="d-flex flex-stack mb-7"> <!--begin::Symbol--> <div class="symbol symbol-60px symbol-2by3 me-4"> <div class="symbol-label" style="background-image: url('/static/assets/images/nopic.gif')"></div> </div> <!--end::Symbol--> <!--begin::Title--> <div class="m-0"> <a href="/news/10567.html" class="text-dark fw-bold text-hover-primary fs-6">客厅放八骏马摆件可以吗(家里摆...</a> <span class="text-gray-600 fw-semibold d-block pt-1 fs-7">今天给各位分享客厅放八骏马摆件可以吗的知识,其中也会对家里摆八骏马摆件好吗进行解释,如果能碰巧解决你...</span> </div> <!--end::Title--> </div> </div> <!--end::Body--> </div> <!--end::Chart Widget 35--> </div> <!--end::Col--> </div> </div> <!--end::Content container--> </div> <!--end::Content--> </div> <!--end::Content wrapper--> <!--begin::Footer--> <div id="kt_app_footer" class="app-footer"> <!--begin::Footer container--> <div class="app-container container-xxl d-flex flex-column flex-md-row flex-center flex-md-stack py-3"> <!--begin::Copyright--> <div class="text-dark order-2 order-md-1"> <span class="text-muted fw-semibold me-1">2025 ©</span> <a href="/" target="_blank" class="text-gray-800 text-hover-primary">银财中国网</a> <a href="https://beian.miit.gov.cn/" target="_blank" class="text-gray-800 text-hover-primary"></a> <a href="http://www.hhfamen.cn">发门网</a><a href="http://www.xingshan.vip">星闪网</a><a href="http://www.yuansudz.com">元素网</a><a href="http://www.taiyangwa.net">太阳生活网</a><a href="http://www.zzszq.net/">深知网</a><a href="http://hcygmm.com.shayuweb.com/">凰巢网</a><a href="http://tv.zzszq.net/">深视网</a><a href="http://code.shayuweb.com/">鲨鱼编程</a> </div> <!--end::Copyright--> <!--begin::Menu--> <ul class="menu menu-gray-600 menu-hover-primary fw-semibold order-1"> <li class="menu-item"> <a href="/news/" target="_blank" class="menu-link px-2">资讯</a> </li> <li class="menu-item"> <a href="/caijing/" target="_blank" class="menu-link px-2">财经</a> </li> <li class="menu-item"> <a href="/shehui/" target="_blank" class="menu-link px-2">社会</a> </li> <li class="menu-item"> <a href="/sitemap.xml" target="_blank" class="menu-link px-2">sitemap</a> </li> </ul> <!--end::Menu--> </div> <!--end::Footer container--> </div> <!--end::Footer--> </div> <!--end:::Main--> </div> <!--end::Wrapper--> </div> <!--end::Page--> </div> <!--end::App--> <div id="kt_scrolltop" class="scrolltop" data-kt-scrolltop="true"> <!--begin::Svg Icon | path: icons/duotune/arrows/arr066.svg--> <span class="svg-icon"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <rect opacity="0.5" x="13" y="6" width="13" height="2" rx="1" transform="rotate(90 13 6)" fill="currentColor"></rect> <path d="M12.5657 8.56569L16.75 12.75C17.1642 13.1642 17.8358 13.1642 18.25 12.75C18.6642 12.3358 18.6642 11.6642 18.25 11.25L12.7071 5.70711C12.3166 5.31658 11.6834 5.31658 11.2929 5.70711L5.75 11.25C5.33579 11.6642 5.33579 12.3358 5.75 12.75C6.16421 13.1642 6.83579 13.1642 7.25 12.75L11.4343 8.56569C11.7467 8.25327 12.2533 8.25327 12.5657 8.56569Z" fill="currentColor"></path> </svg> </span> <!--end::Svg Icon--> </div> <!--begin::Javascript--> <script>var hostUrl = "/static/default/pc/";</script> <!--begin::Global Javascript Bundle(mandatory for all pages)--> <script src="/static/default/pc/plugins/global/plugins.bundle.js"></script> <script src="/static/default/pc/js/scripts.bundle.js"></script> <!--end::Global Javascript Bundle--> <!--end::Javascript--> </body> <!--end::Body--> </html>