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

本文主要是介绍React16源码: React中的updateHostComponent的源码实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

updateHostComponent


1 )概述

  • completeWork 阶段的 HostComponent 处理,继续前文所述
  • 在更新的逻辑里面,调用了 updateHostComponent
  • 进行前后props对应的dom的attributes变化的对比情况
  • 这个方法也是根据不同环境来定义的,我们这里只专注于 react-dom 环境

2 )源码

定位到 packages/react-reconciler/src/ReactFiberCompleteWork.js#L586

这里是 updateHostComponent 调用的地方

updateHostComponent(current,workInProgress,type,newProps,rootContainerInstance,
);if (current.ref !== workInProgress.ref) {markRef(workInProgress);
}

接着,进入其定义

updateHostComponent = function(current: Fiber,workInProgress: Fiber,type: Type,newProps: Props,rootContainerInstance: Container,
) {// If we have an alternate, that means this is an update and we need to// schedule a side-effect to do the updates.const oldProps = current.memoizedProps;// 全等对象,没有变化过,直接 returnif (oldProps === newProps) {// In mutation mode, this is sufficient for a bailout because// we won't touch this node even if children changed.return;}// If we get updated because one of our children updated, we don't// have newProps so we'll have to reuse them.// TODO: Split the update API as separate for the props vs. children.// Even better would be if children weren't special cased at all tho.// 获取 dom 和 contextconst instance: Instance = workInProgress.stateNode;const currentHostContext = getHostContext();// TODO: Experiencing an error where oldProps is null. Suggests a host// component is hitting the resume path. Figure out why. Possibly// related to `hidden`.// 注意这里,const updatePayload = prepareUpdate(instance,type,oldProps,newProps,rootContainerInstance,currentHostContext,);// TODO: Type this specific to this type of component.workInProgress.updateQueue = (updatePayload: any);// If the update payload indicates that there is a change or if there// is a new ref we mark this as an update. All the work is done in commitWork.if (updatePayload) {markUpdate(workInProgress);}
};
  • 进入 prepareUpdate
    // 忽略 DEV 判断,这里直接 return 了 diffProperties 的调用
    export function prepareUpdate(domElement: Instance,type: string,oldProps: Props,newProps: Props,rootContainerInstance: Container,hostContext: HostContext,
    ): null | Array<mixed> {if (__DEV__) {const hostContextDev = ((hostContext: any): HostContextDev);if (typeof newProps.children !== typeof oldProps.children &&(typeof newProps.children === 'string' ||typeof newProps.children === 'number')) {const string = '' + newProps.children;const ownAncestorInfo = updatedAncestorInfo(hostContextDev.ancestorInfo,type,);validateDOMNesting(null, string, ownAncestorInfo);}}return diffProperties(domElement,type,oldProps,newProps,rootContainerInstance,);
    }
    
    • 进入 diffProperties
      // 这个方法和前文中的 setInitialProperties 有一定的类似, 根据不同的节点做不同的操作
      // Calculate the diff between the two objects.
      export function diffProperties(domElement: Element,tag: string,lastRawProps: Object,nextRawProps: Object,rootContainerElement: Element | Document,
      ): null | Array<mixed> {if (__DEV__) {validatePropertiesInDevelopment(tag, nextRawProps);}let updatePayload: null | Array<any> = null;let lastProps: Object;let nextProps: Object;switch (tag) {case 'input':lastProps = ReactDOMInput.getHostProps(domElement, lastRawProps);nextProps = ReactDOMInput.getHostProps(domElement, nextRawProps);updatePayload = [];break;case 'option':lastProps = ReactDOMOption.getHostProps(domElement, lastRawProps);nextProps = ReactDOMOption.getHostProps(domElement, nextRawProps);updatePayload = [];break;case 'select':lastProps = ReactDOMSelect.getHostProps(domElement, lastRawProps);nextProps = ReactDOMSelect.getHostProps(domElement, nextRawProps);updatePayload = [];break;case 'textarea':lastProps = ReactDOMTextarea.getHostProps(domElement, lastRawProps);nextProps = ReactDOMTextarea.getHostProps(domElement, nextRawProps);updatePayload = [];break;default:lastProps = lastRawProps;nextProps = nextRawProps;if (typeof lastProps.onClick !== 'function' &&typeof nextProps.onClick === 'function') {// TODO: This cast may not be sound for SVG, MathML or custom elements.trapClickOnNonInteractiveElement(((domElement: any): HTMLElement));}break;}assertValidProps(tag, nextProps);let propKey;let styleName;let styleUpdates = null;// 那么接下去会有两个循环// 第一个循环是循环 lastProps, 就是上一次渲染的结果产生的props// 这是第一次大循环: 找到在旧的props上有的属性,在新的props上没有的,也就是找到前后对比中删除的propsfor (propKey in lastProps) {// 对 新旧 props 进行循环判断: 新props有key 或 旧props没有key 或 旧props为null 则跳过此keyif (nextProps.hasOwnProperty(propKey) ||!lastProps.hasOwnProperty(propKey) ||lastProps[propKey] == null) {// 符合这个条件,不是被删除的属性,不会加到最后的 updatePayloadcontinue;}// 对符合被删除属性来说,进行删除 style 的操作if (propKey === STYLE) {const lastStyle = lastProps[propKey];for (styleName in lastStyle) {if (lastStyle.hasOwnProperty(styleName)) {if (!styleUpdates) {styleUpdates = {};}styleUpdates[styleName] = '';}}// 后面的其他判断,都没有做什么事情,可以跳过了} else if (propKey === DANGEROUSLY_SET_INNER_HTML || propKey === CHILDREN) {// Noop. This is handled by the clear text mechanism.} else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||propKey === SUPPRESS_HYDRATION_WARNING) {// Noop} else if (propKey === AUTOFOCUS) {// Noop. It doesn't work on updates anyway.} else if (registrationNameModules.hasOwnProperty(propKey)) {// This is a special case. If any listener updates we need to ensure// that the "current" fiber pointer gets updated so we need a commit// to update this element.if (!updatePayload) {updatePayload = [];}} else {// For all other deleted properties we add it to the queue. We use// the whitelist in the commit phase instead.// 注意这里,加入 updatePayload 的方式是 一次加入 key, value 这2个(updatePayload = updatePayload || []).push(propKey, null);}}// 这是第二次大循环, nextProps 就是新的propsfor (propKey in nextProps) {// 处理 新旧 propsconst nextProp = nextProps[propKey];const lastProp = lastProps != null ? lastProps[propKey] : undefined;// 新props没有这个key(这个key在原型链上,不在自己),或 两个 prop 相同,或两个prop 都为 nullif (!nextProps.hasOwnProperty(propKey) ||nextProp === lastProp ||(nextProp == null && lastProp == null)) {// 符合上述条件跳过continue;}// 对于 style 类型的 props 的处理, 判断每个 style 的key是否有变化// 下面会有两个循环来处理if (propKey === STYLE) {if (__DEV__) {if (nextProp) {// Freeze the next style object so that we can assume it won't be// mutated. We have already warned for this in the past.Object.freeze(nextProp);}}if (lastProp) {// Unset styles on `lastProp` but not on `nextProp`.for (styleName in lastProp) {if (lastProp.hasOwnProperty(styleName) &&(!nextProp || !nextProp.hasOwnProperty(styleName))) {if (!styleUpdates) {styleUpdates = {};}styleUpdates[styleName] = '';}}// Update styles that changed since `lastProp`.for (styleName in nextProp) {if (nextProp.hasOwnProperty(styleName) &&lastProp[styleName] !== nextProp[styleName]) {if (!styleUpdates) {styleUpdates = {};}styleUpdates[styleName] = nextProp[styleName];}}} else {// Relies on `updateStylesByID` not mutating `styleUpdates`.if (!styleUpdates) {if (!updatePayload) {updatePayload = [];}updatePayload.push(propKey, styleUpdates);}styleUpdates = nextProp;}} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {const nextHtml = nextProp ? nextProp[HTML] : undefined;const lastHtml = lastProp ? lastProp[HTML] : undefined;if (nextHtml != null) {// 两个html不相等则加入if (lastHtml !== nextHtml) {(updatePayload = updatePayload || []).push(propKey, '' + nextHtml);}} else {// TODO: It might be too late to clear this if we have children// inserted already.}} else if (propKey === CHILDREN) {// 对于 children 的处理if (lastProp !== nextProp &&(typeof nextProp === 'string' || typeof nextProp === 'number')) {// 不同则加入(updatePayload = updatePayload || []).push(propKey, '' + nextProp);}} else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||propKey === SUPPRESS_HYDRATION_WARNING) {// Noop} else if (registrationNameModules.hasOwnProperty(propKey)) {// 这里是事件绑定相关的处理if (nextProp != null) {// We eagerly listen to this even though we haven't committed yet.if (__DEV__ && typeof nextProp !== 'function') {warnForInvalidEventListener(propKey, nextProp);}ensureListeningTo(rootContainerElement, propKey);}if (!updatePayload && lastProp !== nextProp) {// This is a special case. If any listener updates we need to ensure// that the "current" props pointer gets updated so we need a commit// to update this element.updatePayload = [];}} else {// For any other property we always add it to the queue and then we// filter it out using the whitelist during the commit.// 上面都没找到,这个属性是新增属性,直接加入(updatePayload = updatePayload || []).push(propKey, nextProp);}}// 最终处理加入 styleUpdatesif (styleUpdates) {(updatePayload = updatePayload || []).push(STYLE, styleUpdates);}// 最终返回return updatePayload;
      }
      
    • diffProperties 里的 updatePayload 一开始是一个空的数组,并且最终被return
    • 对于 completeWork 来说,拿到的 updatePayload,就是 diffPropertiesprepareUpdate 返回过来的内容
    • 接下去,会有一个判断 if (updatePayload) {} 也就是说空数组也是符合这个判断条件的, 也会执行 markUpdate
    • 上述标记之后,在后续的 commit 的时候进行对应的操作
  • 以上是 updateHostComponent 的过程
    • 具体细节,参考代码里标注的中文文档和源码
    • vdom其实就是这些东西,通过props来进行 attributes 的判断和处理变化的过程
    • 主要是 整个 diffProperties 的处理

这篇关于React16源码: React中的updateHostComponent的源码实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/637806

相关文章

python生成随机唯一id的几种实现方法

《python生成随机唯一id的几种实现方法》在Python中生成随机唯一ID有多种方法,根据不同的需求场景可以选择最适合的方案,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习... 目录方法 1:使用 UUID 模块(推荐)方法 2:使用 Secrets 模块(安全敏感场景)方法

Spring StateMachine实现状态机使用示例详解

《SpringStateMachine实现状态机使用示例详解》本文介绍SpringStateMachine实现状态机的步骤,包括依赖导入、枚举定义、状态转移规则配置、上下文管理及服务调用示例,重点解... 目录什么是状态机使用示例什么是状态机状态机是计算机科学中的​​核心建模工具​​,用于描述对象在其生命

Spring Boot 结合 WxJava 实现文章上传微信公众号草稿箱与群发

《SpringBoot结合WxJava实现文章上传微信公众号草稿箱与群发》本文将详细介绍如何使用SpringBoot框架结合WxJava开发工具包,实现文章上传到微信公众号草稿箱以及群发功能,... 目录一、项目环境准备1.1 开发环境1.2 微信公众号准备二、Spring Boot 项目搭建2.1 创建

IntelliJ IDEA2025创建SpringBoot项目的实现步骤

《IntelliJIDEA2025创建SpringBoot项目的实现步骤》本文主要介绍了IntelliJIDEA2025创建SpringBoot项目的实现步骤,文中通过示例代码介绍的非常详细,对大家... 目录一、创建 Spring Boot 项目1. 新建项目2. 基础配置3. 选择依赖4. 生成项目5.

Linux下删除乱码文件和目录的实现方式

《Linux下删除乱码文件和目录的实现方式》:本文主要介绍Linux下删除乱码文件和目录的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下删除乱码文件和目录方法1方法2总结Linux下删除乱码文件和目录方法1使用ls -i命令找到文件或目录

SpringBoot+EasyExcel实现自定义复杂样式导入导出

《SpringBoot+EasyExcel实现自定义复杂样式导入导出》这篇文章主要为大家详细介绍了SpringBoot如何结果EasyExcel实现自定义复杂样式导入导出功能,文中的示例代码讲解详细,... 目录安装处理自定义导出复杂场景1、列不固定,动态列2、动态下拉3、自定义锁定行/列,添加密码4、合并

mybatis执行insert返回id实现详解

《mybatis执行insert返回id实现详解》MyBatis插入操作默认返回受影响行数,需通过useGeneratedKeys+keyProperty或selectKey获取主键ID,确保主键为自... 目录 两种方式获取自增 ID:1. ​​useGeneratedKeys+keyProperty(推

Spring Boot集成Druid实现数据源管理与监控的详细步骤

《SpringBoot集成Druid实现数据源管理与监控的详细步骤》本文介绍如何在SpringBoot项目中集成Druid数据库连接池,包括环境搭建、Maven依赖配置、SpringBoot配置文件... 目录1. 引言1.1 环境准备1.2 Druid介绍2. 配置Druid连接池3. 查看Druid监控

Linux在线解压jar包的实现方式

《Linux在线解压jar包的实现方式》:本文主要介绍Linux在线解压jar包的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux在线解压jar包解压 jar包的步骤总结Linux在线解压jar包在 Centos 中解压 jar 包可以使用 u

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被