React16源码: React中NewContext的源码实现

NewContext


1 )概述

  • 新的 context API 是一个组件化的使用方式
    • 它就跟写其他的组件一样,像写jsx,通过标签的这种方式来赋值一些props
    • 还有去给子节点去拿到这个 conntext 的属性
  • context的提供方和订阅方都是独立的
    • 在什么地方想要用到这个 context
    • 就去声明式的写这个 consumer 就可以了
    • 而不需要说在这个子树的渲染过程当中
    • 都需要处于这个context的一个环境下面
  • 没有什么附带的性能影响
  • 主要关注源码中的 updateContextProviderupdateContextConsumer
    • 它们就是通过 createContext 返回的两个组件
    • 就是provider和consumer, 它们是一个组件的类型
    • 所以它们会有特定的组件的更新方式
  • 而新的context-api都在 ReactFiberNewContext.js 中
    • 它提供的主要核心的两个api就是 pushProvider 以及 popProvider

2 ) 源码

定位到 packages/react-reconciler/src/ReactFiberBeginWork.js#L1352

function updateContextProvider(current: Fiber | null,workInProgress: Fiber,renderExpirationTime: ExpirationTime,
) {// 在这个更新的过程中,去获取了 workingprogress点type// 这个type就是我们通过createcontext返回的那个provider对象// 这个对象上面会有一个属性指向 consumer 那个typeconst providerType: ReactProviderType<any> = workInProgress.type; // 这里获得的就是 provider 组件const context: ReactContext<any> = providerType._context; // 这里获得的 context 就是 consumer// 拿到前后两个 propsconst newProps = workInProgress.pendingProps;const oldProps = workInProgress.memoizedProps;const newValue = newProps.value;// 跳过if (__DEV__) {const providerPropTypes = workInProgress.type.propTypes;if (providerPropTypes) {checkPropTypes(providerPropTypes,newProps,'prop','Context.Provider',ReactCurrentFiber.getCurrentFiberStackInDev,);}}// 注意这里pushProvider(workInProgress, newValue);if (oldProps !== null) {const oldValue = oldProps.value;const changedBits = calculateChangedBits(context, newValue, oldValue);if (changedBits === 0) {// 没有更新// No change. Bailout early if children are the same.if (oldProps.children === newProps.children &&!hasLegacyContextChanged()) {return bailoutOnAlreadyFinishedWork(current,workInProgress,renderExpirationTime,);}} else {// 存在更新// The context value changed. Search for matching consumers and schedule// them to update.propagateContextChange(workInProgress,context,changedBits,renderExpirationTime,);}}const newChildren = newProps.children;reconcileChildren(current, workInProgress, newChildren, renderExpirationTime);return workInProgress.child;
}
  • 关于 const context: ReactContext<any> = providerType._context;
    // packages/react/src/ReactContext.js#L53
    // 在 createContext 函数内
    context.Provider = {$$typeof: REACT_PROVIDER_TYPE,_context: context,
    };
    
  • 进入 pushProvider
    // packages/react-reconciler/src/ReactFiberNewContext.js#L55
    export function pushProvider<T>(providerFiber: Fiber, nextValue: T): void {const context: ReactContext<T> = providerFiber.type._context;// isPrimaryRenderer 来自 ReactFiberHostConfig.js// 这个值在 dom环境中是 trueif (isPrimaryRenderer) {// 这个 valueCursor 记录的是 当前这个树下面一共经历了几个provider,它对应的值// consumer 也就是 context 它的 value 去获取,是通过赋制在它上面的这个 _currentValue 来进行一个获取的push(valueCursor, context._currentValue, providerFiber);// 所以, 这跟最终去获取这个 consumer 上面的 context 的时候,跟这个 valueCursor 是没有任何关系的context._currentValue = nextValue;if (__DEV__) {warningWithoutStack(context._currentRenderer === undefined ||context._currentRenderer === null ||context._currentRenderer === rendererSigil,'Detected multiple renderers concurrently rendering the ' +'same context provider. This is currently unsupported.',);context._currentRenderer = rendererSigil;}} else {push(valueCursor, context._currentValue2, providerFiber);context._currentValue2 = nextValue;if (__DEV__) {warningWithoutStack(context._currentRenderer2 === undefined ||context._currentRenderer2 === null ||context._currentRenderer2 === rendererSigil,'Detected multiple renderers concurrently rendering the ' +'same context provider. This is currently unsupported.',);context._currentRenderer2 = rendererSigil;}}
    }
    
  • 进入 calculateChangedBits
    export function calculateChangedBits<T>(context: ReactContext<T>,newValue: T,oldValue: T,
    ) {// Use Object.is to compare the new context value to the old value. Inlined// Object.is polyfill.// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is// 使用这种方法可更准确判断两个值是否相等, 符合下面条件,被认为是全等的if ((oldValue === newValue &&(oldValue !== 0 || 1 / oldValue === 1 / (newValue: any))) ||(oldValue !== oldValue && newValue !== newValue) // eslint-disable-line no-self-compare) {// No change 没有变化return 0;} else {// 注意,这里 _calculateChangedBits 这个API未开放,直接忽略即可const changedBits =typeof context._calculateChangedBits === 'function'? context._calculateChangedBits(oldValue, newValue): MAX_SIGNED_31_BIT_INT;if (__DEV__) {warning((changedBits & MAX_SIGNED_31_BIT_INT) === changedBits,'calculateChangedBits: Expected the return value to be a ' +'31-bit integer. Instead received: %s',changedBits,);}return changedBits | 0; // | 0 这里是去除小数部分}
    }
    
    • 上述 (oldValue !== 0 || 1 / oldValue === 1 / (newValue: any) 表示 -0 !== +0
    • 上述 oldValue !== oldValue && newValue !== newValue 表示 NAN !== NAN
    • 上述 MAX_SIGNED_31_BIT_INT 转换成2进制就是 32 位 的 1
    • 以上判断就是 除了全等就是 MAX_SIGNED_31_BIT_INT
  • 进入 propagateContextChange
    export function propagateContextChange(workInProgress: Fiber,context: ReactContext<mixed>,changedBits: number,renderExpirationTime: ExpirationTime,
    ): void {// 拿到 当前 provider 的第一个子节点let fiber = workInProgress.child;if (fiber !== null) {// Set the return pointer of the child to the work-in-progress fiber.fiber.return = workInProgress;}// 对子节点进行遍历while (fiber !== null) {let nextFiber;// Visit this fiber.let dependency = fiber.firstContextDependency; // 读取这个属性// 存在,则进入循环if (dependency !== null) {do {// Check if the context matches.// changedBits 是32位的二进制数都是1// 只要 dependency.observedBits 不是0,dependency.observedBits & changedBits 就不是 0// dependency.context === context 表示遍历过程中的组件是依赖于这个当前的context的// 如果这个context的变化,那么说明它要重新渲染// 同时它去判断它提供的这个 observedBits 跟 changedBits 它们是有相交的部分的// 说明它依赖的部分也变化了, 通过这种方式判断这个组件其实是需要更新了if (dependency.context === context &&(dependency.observedBits & changedBits) !== 0) {// Match! Schedule an update on this fiber.// 这个组件如果需要更新,除非是他自己调用了setState来创建了一个更新// 不然的话没有外部的方式让他去可以更新这种情况// 因为我们在 beginWork 的时候开始是要判断每一个组件自己的 expirationTime 的// 如果那个组件它本身没有创建过更新,那么它的 expirationTime 是 nowork// 是nowork的话,它就直接跳过更新了,这明显不符合我们这边的一个context的一个需求// context这边它就遍历到这个节点的时候,发现它依赖这个context,如何更新?// 通过主动去创建 update,并且设置你的 update.tag 是 ForceUpdate// 这其实没有state的一个更新,但是你必须要更新// 因为依赖了这个context,context更新了,所以强制更新一下// 然后把这个update执行 enqueueUpdate 一下,这样的话// 在后续要更新到这个组件的时候,它就会发现它上面是有update的, 需要去更新它if (fiber.tag === ClassComponent) {// Schedule a force update on the work-in-progress.const update = createUpdate(renderExpirationTime);update.tag = ForceUpdate;// TODO: Because we don't have a work-in-progress, this will add the// update to the current fiber, too, which means it will persist even if// this render is thrown away. Since it's a race condition, not sure it's// worth fixing.enqueueUpdate(fiber, update);}// 同时这边创建 update 是不够的,还要对这个 fiber 它的 expirationTime 进行一个操作// 要看它,如果目前它的 expirationTime 的优先级是要大于我当前正在渲染这次 expirationTime 的// 那么我就把它的 expirationTime 设置为这次 update,让它在这次渲染过程当中,肯定会被更新到if (fiber.expirationTime < renderExpirationTime) {fiber.expirationTime = renderExpirationTime;}// 不仅要在这个 workingprogress 上面去设置,我还要对它的 alternate,也就是current也要进行一个设置// 因为这个东西它们应该是要被同步的let alternate = fiber.alternate;if (alternate !== null &&alternate.expirationTime < renderExpirationTime) {alternate.expirationTime = renderExpirationTime;}// Update the child expiration time of all the ancestors, including// the alternates.// 因为在这个过程当中给这个组件创建了一个update// 代表的意思是,我父链上面的每一个节点,它的 childExpirationTime 有可能会被改变// 同样的要执行一遍,类似于我们在 scheduleWorkToRoot 的时候的做的事情,就是设置它的 childExpirationTimelet node = fiber.return;while (node !== null) {alternate = node.alternate;if (node.childExpirationTime < renderExpirationTime) {node.childExpirationTime = renderExpirationTime;if (alternate !== null &&alternate.childExpirationTime < renderExpirationTime) {alternate.childExpirationTime = renderExpirationTime;}} else if (alternate !== null &&alternate.childExpirationTime < renderExpirationTime) {alternate.childExpirationTime = renderExpirationTime;} else {// Neither alternate was updated, which means the rest of the// ancestor path already has sufficient priority.break;}node = node.return;}}// 最后设置nextFiber = fiber.child;dependency = dependency.next; // 从这里可以看出 dependency 它也是可以有多个的} while (dependency !== null);} else if (fiber.tag === ContextProvider) {// Don't scan deeper if this is a matching providernextFiber = fiber.type === workInProgress.type ? null : fiber.child;} else {// Traverse down.nextFiber = fiber.child;}// 接下去和很多地方差不多的,就是它往子树上去找// 如果子树上没有了,它就往它的兄弟节点去找// 就是相当于要把我们的这个provider当前的这个组件,它的子树的每一个节点去遍历到// 并且找到有 firstContextDependency 这个属性的这些节点// 给它去创建更新的一个过程if (nextFiber !== null) {// Set the return pointer of the child to the work-in-progress fiber.nextFiber.return = fiber;} else {// No child. Traverse to next sibling.nextFiber = fiber;while (nextFiber !== null) {if (nextFiber === workInProgress) {// We're back to the root of this subtree. Exit.nextFiber = null;break;}let sibling = nextFiber.sibling;if (sibling !== null) {// Set the return pointer of the sibling to the work-in-progress fiber.sibling.return = nextFiber.return;nextFiber = sibling;break;}// No more siblings. Traverse up.nextFiber = nextFiber.return;}}fiber = nextFiber;}
    }
    
    • 关于 firstContextDependency
      • 在 packages/react-reconciler/src/ReactFiberBeginWork.js#L1415 中的 updateContextConsumer 函数中
        • 调用了 prepareToReadContext 函数, 在这个函数中 workInProgress.firstContextDependency = null;
      • 接下去调用了 readContext 获取到 newValue
        • lastContextDependency 为 null 的时候
          • 设置了 currentlyRenderingFiber.firstContextDependency = lastContextDependency = contextItem;
        • 这是第一次进来的逻辑,对于 consumer 的情况已经够了,只会调这么一次,一个consumer 对应一个 provider 的,只会有一个context
        • 这里面有很多变量控制和单项链表的数据结构来进行存储不同的 context
        • 猜测是为了实现支持 hooks 的环境里面,一个 function component里面是可以读取多个context的使用的
        • 就是 useContext 这个API, 读取多个context,说明这个function component是依赖于多个context provider
        • 那么这个时候我们就要在这个 firstcontextdependency 上面去设置一个链表了
        • 能够让我们在有多个context的情况下,每个context变化都能让这个function component去执行一次更新
        • 后续的contextitem直接设置为当前的这个next
        • 因为这个contextItem里面是有个next的指针的指向一个它依赖的context就可以了
      • 拿到 newValue 之后,调用 render 拿到 newChildren
      • 之后,reconcileChildren 就完成了,这就是 consumer 的一个更新过程
    • 注意,在之前的 packages/react-reconciler/src/ReactFiberClassComponent.js#L997 里面
      • const contextType = ctor.contextType; 声明了一个叫做 contextType 的这么一个属性
      • 这个属性拿到之后,就可以去 readContext
    • 对于 updateClassComponent packages/react-reconciler/src/ReactFiberBeginWork.js#L428
      • 也会调用 prepareToReadContext
      • 所以对于 class component 这个fiber对象,也会给它设置 firstContextDependency 这个属性的
    • 这就是在 propagateContextChange 这个方法里面可以看到
      • 它只有对于class component它才会去创建一个update
      • 对于consumer它是不会去创建update的,因为consumer它不是update的一个载体
      • 我们只需需要给它设置 expirationTime 就可以了, 不需要给它建update来告诉它,我需要 forceUpdate
      • 因为在 consumer 中如果需要更新,就会执行更新的流程
      • 它没有像 class component,可以通过 scu 这种方式的规避一个组件的更新的流程

总结

  • 以上就是新的context API它的一个实现过程
  • 最主要的就是通过去更新 provider 的时候,给这个context它的 _currentValue 设置了一个值
  • 设置的这个值就是新的context,它提供的这个value,以此让我们的consumer可以去获取到这个值
  • 可以看到它 readcontext 的时候,最后是有一句代码的
  • return isPrimaryRenderer ? context._currentValue : context._currentValue2;
  • 这个才是真正返回值的情况, 前面是处理它这个context的依赖
  • 对于 provider 更新了之后,如何去通知一个consumer依赖的那个组件
  • 通过手动的去遍历它的所有子节点, 然后去给它指定它的 expirationTime,告诉后续的更新的流程里面
  • 这个依赖于我的context,所以它需要去更新
  • 而且对于 class component,要单独为它去创建一个update
  • 这就是 provider 和 consumer 这种新的 context API 它的一个实现过程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/657383.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《Numpy 简易速速上手小册》第5章:Numpy高效计算与广播(2024 最新版)

文章目录 5.1 向量化计算5.1.1 基础知识5.1.2 完整案例&#xff1a;股票数据分析5.1.3 拓展案例 1&#xff1a;多维数组运算5.1.4 拓展案例 2&#xff1a;复杂函数的向量化应用 5.2 广播机制5.2.1 基础知识5.2.2 完整案例&#xff1a;二维数据与一维数据运算5.2.3 拓展案例 1&a…

vxe-table表格合并行和虚拟滚动冲突

项目一直用的vxe-table 2.0版本&#xff0c;支持表格的虚拟滚动&#xff0c;最近要做表格合并行功能&#xff0c;虚拟滚动便失效了&#xff0c;强行虚拟滚动&#xff0c;合并行会有错行现象。 vxe-table2.0给出的解释是&#xff1a;合并行不能和虚拟滚动一起使用。 目前找到两种…

华为VRP系统简介

因为现在国内主流是华为、华三、锐捷的设备趋势&#xff0c;然后考的证书也是相关的&#xff0c;对于华为设备的一个了解也是需要的。 一、VRP概述 华为的VRP(通用路由平台)是华为公司数据通信产品的通用操作系统平台&#xff0c;作为华为公司从低端到核心的全系列路由器、以太…

个人建站前端篇(一)项目准备初始化以及远程仓库连接

云风的知识库 云风网前端重构&#xff0c;采用vue3.0vite antd框架&#xff0c;实现前后端分离&#xff0c;实现网站的SEO优化&#xff0c;实现网站的性能优化 vite创建vue项目以及前期准备 Vite 需要 Node.js 版本 18&#xff0c;20。然而&#xff0c;有些模板需要依赖更高…

java生成dll,并利用c语言使用libcurl调用http接口

本文可能需要使用的环境和工具&#xff1a; c/ c和GCC编译器 (Windows) Cygwin或MinGW 本文运行环境为windows10&#xff0c;使用MinGW-W64-builds-4.2.0 curl-8.5.0 libcurl 可以在官网 http://curl.haxx.se/ 获得。 配置MinGW 将mingw.rar解压到D:&#xff0c;修改系统…

Java面试题之 IO(四)

Java面试题之 IO&#xff08;四&#xff09; 文章目录 Java面试题之 IO&#xff08;四&#xff09;随机访问流 文章来自Java Guide 用于学习如有侵权&#xff0c;立即删除 随机访问流 这里要介绍的随机访问流指的是支持随意跳转到文件的任意位置进行读写的 RandomAccessFile 。…

Java抽取Hive、HDFS元数据信息

文章目录 一、技术二、构建SpringBoot工程2.1 创建maven工程并配置 pom.xml文件2.2 编写配置文件 application.yml2.3 编写配置文件 application.propertites2.4 开发主启动类2.5 开发配置类 三、测试抽取Hive、HDFS元数据四、将抽取的元数据存储到MySQL4.1 引入依赖4.2 配置ap…

防火墙综合拓扑(NAT、双机热备)

实验需求 拓扑 实验注意点&#xff1a; 先配置双机热备&#xff0c;再来配置安全策略和NAT两台双机热备的防火墙的接口号必须一致如果其中一台防火墙有过配置&#xff0c;最好清空或重启&#xff0c;不然配置会同步失败两台防火墙同步完成后&#xff0c;可以直接在主状态防火墙…

浅谈WPF之UniformGrid和ItemsControl

在日常开发中&#xff0c;有些布局非常具有规律性&#xff0c;比如相同的列宽&#xff0c;行高&#xff0c;均匀的排列等&#xff0c;为了简化开发&#xff0c;WPF提供了UniformGrid布局和ItemsControl容器&#xff0c;本文以一个简单的小例子&#xff0c;简述&#xff0c;如何…

TSINGSEE青犀智能分析网关V4—让加油站迈入AI智能检测时代

一、背景与需求 中国目前建设加油站超过10万个&#xff0c;作为高危场所对于烟火&#xff0c;危险区域管控、消防器材等管理要求严格&#xff0c;稍有不慎即酿成大祸。由于春节临近&#xff0c;加油站各类人员进出频繁&#xff0c;安全意识较低&#xff0c;依靠普通监控人力的…

java常量和kotlin常量

在java中使用final声明常量在kotlin中使用const val声明常量 常量在编译为字节码后会直接把调用常量的地方直接替换为常量值&#xff0c;示例如下&#xff1a; public class ConstDemo {public static final String NAME "Even";private static final int ID 100…

海外云手机开辟企业跨境电商新道路

近几年&#xff0c;海外云手机为跨境电商、海外媒体引流、游戏行业等互联网领域注入了蓬勃活力。对于国内跨境电商而言&#xff0c;在亚马逊及其他平台上&#xff0c;短视频引流和社交电商营销成为最为有效的流量来源。如何通过海外云手机的助力&#xff0c;在新兴社交平台为企…

python二维高斯热力图绘制简单的思路代码

import numpy as np import matplotlib.pyplot as plt from scipy.ndimage import gaussian_filter import cv2# 生成一个示例图像 image_size 100 image np.zeros((image_size, image_size))# 在图像中心创建一个高亮区域 center_x, center_y image_size // 2, image_size …

【遥感专题系列】遥感影像信息提取之——人工目视解译

​遥感影像通过亮度值或像元值的高低差异&#xff08;反映地物的光谱信息&#xff09;及空间变化&#xff08;反映地物的空间信息&#xff09;来表示不同地物的差异&#xff0c;这是区分不同影像地物的物理基础。 ​人工解译是目前国内使用最多的一种影像提取方法&#xff0c;如…

力扣hot100 单词搜索 深度优先搜索 特殊字符判重

Problem: 79. 单词搜索 Code class Solution{int n, m;char[][] b;String word;int[] dx { 1, 0, -1, 0 };int[] dy { 0, 1, 0, -1 };public boolean exist(char[][] board, String word){b board;this.word word;n b.length;m b[0].length; // 以所有点作为起点来进行…

oracle rman duplicate创建测试库

1.在目标端建立参数文件&#xff0c;并启动到nomount&#xff0c;如果测试库的文件存放路径和生产不一致&#xff0c;配置db_file_name_convert和log_file_name_convert 2.拷贝生产的密码文件到目标端&#xff0c;配置生产到目标端的tnsnames 3.配置目标端的监听为静态监听&a…

生词本----Python实例练习

题目描述 背单词是英语学习中最基础的一环&#xff0c;不少学生在背诵单词的过程中会整理自己的生词本&#xff0c;以不断拓展自己的词汇量。本实例要求编写生词本程序&#xff0c;该程序需具备以下功能。 &#xff08;1&#xff09;查看生词列表功能&#xff1a;输出生词本中…

系统分析师-22年-下午题目

系统分析师-22年-下午题目 更多软考知识请访问 https://ruankao.blog.csdn.net/ 试题一必答&#xff0c;二、三、四、五题中任选其中两题作答 试题一 (25分) 说明 某软件公司拟开发一套博客系统&#xff0c;要求能够向用户提供一个便捷发布自已心得&#xff0c;及时有效的…

腾讯云SDK并发调用优化方案

目录 一、概述 二、 网关的使用 2.1 核心代码 三、腾讯云SDK依赖包的改造 一、概述 此网关主要用于协调腾讯云SDK调用的QPS消耗&#xff0c;使得多个腾讯云用户资源能得到最大限度的利用。避免直接使用腾讯云SDK 时&#xff0c;在较大并发情况下导致接口调用异常。网关的工…

Python与CAD系列高级篇(二十五)分类提取坐标到excel(补充圆半径、线长度、圆弧)

目录 0 简述1 分类提取坐标到excel2 结果展示0 简述 上一篇中介绍了:对点、直线、多段线、圆、样条曲线分类读取坐标并提取到excel。考虑到进一步提取图形信息,此篇补充对圆半径、线长度以及圆弧几何信息的提取。 1 分类提取坐标到excel 代码实现: import math import nump…