前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >小前端读源码 - React16.7.0(二)

小前端读源码 - React16.7.0(二)

作者头像
LamHo
发布2022-09-26 10:37:19
3490
发布2022-09-26 10:37:19
举报
文章被收录于专栏:小前端看世界

上一篇文章说到React代码经过编译后,会将JSX的语法都经过react.createElement函数转换成一个对象传入的ReactDOM.render中。本章将会接着阅读ReactDOM.render中是如何将元素生成虚拟DOM以及如何渲染到页面中的。

Lam:小前端读源码 - React16.7.0(一)

接着上一章说到的,去看看ReactDOM里面到底有什么。从源码当中我们发现ReactDOM提供了一些属性和方法。其中的作用自行查文档了。

  • createPortal
  • findDOMNode
  • hydrate
  • render
  • unstable_renderSubtreeIntoContainer
  • unmountComponentAtNode
  • unstable_createPortal
  • unstable_batchedUpdates
  • unstable_interactiveUpdates
  • flushSync
  • unstable_createRoot -> React 17版本将会废除
  • unstable_flushControlled
  • __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED

我们经常使用的一般都是ReactDOM.render这个API,将我们的组件渲染到页面中,我们就一起看看render里面到底做了什么事情吧!

首先render接受3个参数,element、container、callback。从React16版本开始,element就是我们经过react.createElement后返回的对象。container就是我们需要渲染到的元素。render将不会return会组件对象了,改为在callback中返回。在render中会将数据传入一个叫做legacyRenderSubtreeIntoContainer的方法中。

legacyRenderSubtreeIntoContainer

首先legacyRenderSubtreeIntoContainer会检查传入的container的类型,如果传入的类型不符合规定将会报错。那么可以传入什么类型呢?

代码语言:javascript
复制
function isValidContainer(node) {
  return !!(node && (node.nodeType === ELEMENT_NODE || node.nodeType === DOCUMENT_NODE || node.nodeType === DOCUMENT_FRAGMENT_NODE || node.nodeType === COMMENT_NODE && node.nodeValue === ' react-mount-point-unstable '));
}

react会检测传入的container的nodeType是需要等于1、9、11或者8,如果nodeType等于8的情况下,还需要nodeValue必须等于" react-mount-point-unstable "。

并且如果controller是body的话,也会出现waring提示!

代码语言:javascript
复制
!(container.nodeType !== ELEMENT_NODE || !container.tagName || container.tagName.toUpperCase() !== 'BODY') ? warningWithoutStack$1(false, 'render(): Rendering components directly into document.body is ' + 'discouraged, since its children are often manipulated by third-party ' + 'scripts and browser extensions. This may lead to subtle ' + 'reconciliation issues. Try rendering into a container element created ' + 'for your app.') : void 0;

接着react会判断你当前传入的contrainer是不是已经是一个react的根组件,会通过判断传入的contrainer中是否存在_reactRootContainer这个对象进行判断,react会在渲染的同时将_reactRootContainer注入到contrainer对象中。

并且会根据是否存在_reactRootContainer进行不同的渲染方式,我们先看当前的contrainer是没有渲染过任何reactElement的情况下如何执行的。

接着在legacyRenderSubtreeIntoContainer函数中会执行几个比较重要的函数将传入的container和children以及children中的children一一渲染的函数了,最终将组件对象返回的一系列操作。

  1. legacyCreateRootFromDOMContainer
  2. unbatchedUpdates
  3. render / legacy_renderSubtreeIntoContainer

legacyCreateRootFromDOMContainer

如果当前传入的container并不是一个已经初始化的容器,那么将会执行legacyCreateRootFromDOMContainer这个函数,那么从源码也看到了,在执行完legacyCreateRootFromDOMContainer之后其实就会将返回的值赋值到container中的_reactRootContainer了。如果之后还对同一个container进行render的话,就会判断到存在_reactRootContainer这一个对象,那么就会进入这个判断中了。那么legacyCreateRootFromDOMContainer到底帮我们做了什么事情呢?我们先看看它返回了什么东西回来。

好吧,我们并不知道里面的是什么,那么只能看看legacyCreateRootFromDOMContainer函数里面执行了什么东西了。

首先react需要判断你是不是服务器渲染,其实早在ReactDOM对象内到legacyCreateRootFromDOMContainer之间有很多关于服务器渲染的判断,但是我们现在目标是先搞懂react在浏览器渲染的流程和逻辑,所以我们会先跳过一些服务器渲染的流程和逻辑。那么legacyCreateRootFromDOMContainer一开始就会通过传入的forceHydrateshouldHydrateDueToLegacyHeuristic去判断是不是服务器渲染,那么结果当然是false啦。那么就会进入到一个清楚container内容的判断中。

清楚内容的逻辑是先获取到container的lastChild,然后判断lastChild是否为一个元素,并且这个元素不能带有data-reactroot这个属性,否则报错。然后删除掉这个子元素,这是一个循环直到container的lastChild为null才会停止。

有时候我们需要在react.js和业务js加载前出现一些占位图或者loading图片这一些提高首屏的方式,那么就无可避免的在contrainer里面写入一些默认的html标签去实现占位样式了。

代码语言:javascript
复制
<div id="root">
   <p>占位</p>
   <p>占位</p>
   <p>占位</p>
   ...
</div>

这样就会导致在legacyCreateRootFromDOMContainer中需要删除container内的子元素要循环多次,所以一个优化的点就是把里面同级的内容包在一个元素中,那么只需要循环一次就可以了。

代码语言:javascript
复制
<div id="root">
   <div>
      <p>占位</p>
      <p>占位</p>
      <p>占位</p>
       ...
   </div>
</div>

这里其实也只是判断是否为服务器渲染。

最后就将参数传入ReactRoot并实例化ReactRoot后返回。

ReactRoot

从源码看到ReactRoot这个构造函数就是通过一系列的函数初始化了一堆属性(应该是属于状态之类的变量)。然后赋值到this._internalRoot中。执行的函数顺序如下:

  1. createContainer
  2. createFiberRoot

因为react在16.2就已经修改为了Fiber架构,所以这里createFiberRoot只是其中一个创建Fiber一种方式而已。

暂时我们先用到的是createHostRootFiber这个函数。所有的fiber都是FiberNode的实例。

最终输出的就是一开始我们看见的那个对象。

当然ReactRoot的原型上有以下4个API:

  • render
  • unmount
  • legacy_renderSubtreeIntoContainer
  • createBatch

我们常用的估计也就render和unmount这两个了。而legacy_renderSubtreeIntoContainer和createBatch这两个API在文档中其实也没有说明。

到这位置其实就是整个container的_reactRootContainer初始化过程了,那么我们就回到legacyRenderSubtreeIntoContainer这个函数中继续往下看渲染过程了。

legacyRenderSubtreeIntoContainer会对我们ReactDOM.render传入的第三个参数(回到函数)进行一个包装。最终返回的是this._internalRoot.current.child.stateNode。

接着就是一个批处理的判断,但是还没有发现这个批处理是什么情况会使用,我们先忽略它。

到这里为止,其实都是创建关键的root根对象。接下来就是root.render将要渲染到根对象中的App的ReactElement对象进行一些操作了。

root.render

需要关注的是在render函数内有2个地方是需要注意的:

  • ReactWork
  • updateContainer

ReactWork是什么东西呢?

其实ReactWork是一个很简单的东西,它有两个值_callbacks和_didCommit。通过执行then函数传入callback,如果判断到当前的_didCommit为false的情况下,就将callback添加到_callbacks数组内。然后通过执行_onCommit去改变_didCommit的值,之后循环执行_callbacks中的callback。

updateContainer

render函数之后会执行updateContainer函数,传入children,root和work实例化后的_onCommit函数。因为这个render其实是root根对象上的render,所以children就是App(当然也可以是其他,视乎你执行ReactDOM.render时传入的第一个参数是什么)。

在updateContainer中会通过requestCurrentTime和computeExpirationForFiber得出currentTime和expirationTime这个两个时间之后传入到updateContainerAtExpirationTime中,之后再传入到scheduleRootUpdate中。

scheduleRootUpdate会将expirationTime传入一个createUpdate函数中创建一个update对象。并且将element赋值到update.payload中(element就是App的ReactElement),并且将callback赋值到update.callback中。

接着会执行enqueueUpdate函数,这个函数其实大概的意思就是将新建的update对象和当前的FiberNode对象传入,然后为current$$1这个对象添加了updateQueue对象,里面保存着相关的一些任务。以下是返回结果:

之后就执行scheduleWork函数。曾经断点开过这个函数执行完之后,页面就会渲染出dom节点了并且回调函数也执行了。无比兴奋。

其实到源码看到这里发现很多问题,例如react很喜欢用全局变量,而且里面发现其实为了之后的异步渲染做了不少准备的,很多的判断代码。开始有点怀疑是不是应该读16.7.0版本的代码,但是已经开始了,那就继续吧。

总结

整个流程是比较复杂,中间很多对象之间的引用,又实例一些对象,如果单看上面的流程比较懵逼的话,没有关系,我在这里梳理一下整个流程,最终传入scheduleWork前的参数是怎么生成出来的,原来的container和children去哪里了呢?我们通过一个流程图去说明整个流程是怎样的。

下一篇继续说如何渲染到真实DOM当中的。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-01-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • legacyRenderSubtreeIntoContainer
  • legacyCreateRootFromDOMContainer
  • ReactRoot
  • root.render
  • updateContainer
  • 总结
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档