资讯专栏INFORMATION COLUMN

ReactDOM.render源码解析-1

_Zhao / 1027人阅读

摘要:本文将对源码做一个初步解析。首先在方法中校验参数是否合法,然后调用在中,调用拿到了的一个实例,调用拿到了,用于注入到,和作为返回值,调用开始调度过程在中,首先清理了中的所有子节点,然后了一个并返回是如何调度的是一个什么样的类的操作是在哪里

</>复制代码

  1. 初步看了react-dom这个包的一些源码,发现其比react包要复杂得多,react包中基本不存在跨包调用的情况,他所做的也仅仅是定义了ReactElement对象,封装了对ReactElement的基本操作,而react-dom包存在复杂的函数调用。本文将对ReactDOM.render源码做一个初步解析。
    文章中如有不当之处,欢迎交流指点。react版本16.8.2。在源码添加的注释在githubreact-source-learn
前言

使用react时常常写类似下面的代码:

</>复制代码

  1. import ReactDOM from "react-dom";
  2. ReactDOM.render(
  3. Hello, world!

    ,
  4. document.getElementById("root")
  5. );

代码1

这里导入的ReactDOM就是packages/react-dom/client/ReactDOM.js中所导出的对象。从文档可见ReactDOM对象有如下几个方法:(ps:从源码看其实还有很多其他方法)

render()

hydrate()

unmountComponentAtNode()

findDOMNode()

createPortal()

本文只介绍render()方法

代码分析

render方法定义如下:

</>复制代码

  1. render(
  2. element: React$Element,
  3. container: DOMContainer,
  4. callback: ?Function,
  5. ) {
  6. invariant(
  7. // 1
  8. isValidContainer(container),
  9. "Target container is not a DOM element.",
  10. );
  11. if (__DEV__) {
  12. warningWithoutStack(
  13. !container._reactHasBeenPassedToCreateRootDEV,
  14. "You are calling ReactDOM.render() on a container that was previously " +
  15. "passed to ReactDOM.%s(). This is not supported. " +
  16. "Did you mean to call root.render(element)?",
  17. enableStableConcurrentModeAPIs ? "createRoot" : "unstable_createRoot",
  18. );
  19. }
  20. // 2
  21. return legacyRenderSubtreeIntoContainer(
  22. null,
  23. element,
  24. container,
  25. false,
  26. callback,
  27. );
  28. },

代码2

render方法接收两个必选参数可一个可选参数,结合代码1的调用可知,element是一个ReactElement对象, container是一个dom节点,callback在上面的代码1并没有指定,他是一个可选函数。

这个render方法做的事情比较简单,一是校验container参数,二是调用legacyRenderSubtreeIntoContainer方法并返回。

接下来是legacyRenderSubtreeIntoContainer

</>复制代码

  1. // 删除了第一次调ReactDOM.render不会走的分支
  2. function legacyRenderSubtreeIntoContainer(
  3. parentComponent: ?React$Component, // ReactDOM.render 是null
  4. children: ReactNodeList, // 是一个ReactElement , ReactDOM.render是第一个参数
  5. container: DOMContainer, // 是一个dom节点, ReactDOM.render是第二个参数
  6. forceHydrate: boolean, // ReactDOM.render 是false
  7. callback: ?Function, // ReactDOM.render 是 第三个参数
  8. ) {
  9. if (__DEV__) {
  10. topLevelUpdateWarnings(container);
  11. }
  12. // TODO: Without `any` type, Flow says "Property cannot be accessed on any
  13. // member of intersection type." Whyyyyyy.
  14. // 根据type知道, Root type是个对象,包含
  15. // render方法
  16. // unmount方法
  17. // legacy_renderSubtreeIntoContainer 方法
  18. // createBatch 方法
  19. // _internalRoot属性
  20. let root: Root = (container._reactRootContainer: any);
  21. if (!root) { // ReactDOM.render调用时走这里
  22. // Initial mount
  23. // 调用 legacyCreateRootFromDOMContainer 拿 Root
  24. root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
  25. container,
  26. forceHydrate, // ReactDOM.render是false
  27. );
  28. // 在callback加参数
  29. if (typeof callback === "function") {
  30. const originalCallback = callback;
  31. callback = function() {
  32. const instance = getPublicRootInstance(root._internalRoot);
  33. originalCallback.call(instance);
  34. };
  35. }
  36. // Initial mount should not be batched.
  37. // 这个是packages/react-reconciler/ReactFiberScheduler.js中的方法
  38. // TOLEARN: 这个里边应该是一些调度过程, 后续再看
  39. unbatchedUpdates(() => {
  40. if (parentComponent != null) {
  41. root.legacy_renderSubtreeIntoContainer(
  42. parentComponent,
  43. children,
  44. callback,
  45. );
  46. } else { // ReactDOM.render方法走这里
  47. // 这里的root.render 返回的是一个叫Work的东西, TOLEARN,这个Work后面再做了解
  48. root.render(children, callback);
  49. }
  50. });
  51. }
  52. // getPublicRootInstance是/packages/react-reconciler/ReactFiberReconciler.js中的方法
  53. // 关于他是返回的一个什么东西, 后面再看, 总之他是我ReactDOM.render方法回调函数的一个参数,
  54. // 也是返回值
  55. return getPublicRootInstance(root._internalRoot);
  56. }

legacyRenderSubtreeIntoContainer在第一次render时做了如下事情:

调用legacyCreateRootFromDOMContainer拿到一个ReactRoot的实例

在callback中注入一个参数instance

调用unbatchedUpdates开始一轮调度过程,这个是猜的

返回instance

关于instance的获取是调用的getPublicRootInstance,getPublicRootInstance是/packages/react-reconciler/ReactFiberReconciler.js中的方法,后面再研究

关于unbatchedUpdates, 这个东西是这个是packages/react-reconciler/ReactFiberScheduler.js中的方法,简单看了看还没太明白

接下来看一下ReactRoot实例的获取

</>复制代码

  1. // 删除了__DEV__分支的代码
  2. // 若需要清理container的子节点,清理, 然后new ReactRoot并返回
  3. function legacyCreateRootFromDOMContainer(
  4. container: DOMContainer, // dom节点
  5. forceHydrate: boolean, // false render
  6. ): Root {
  7. // 是否不需要清理container的子元素, 第一次render是false, 即需要清理
  8. const shouldHydrate =
  9. forceHydrate || shouldHydrateDueToLegacyHeuristic(container); // 第一次调用时为false
  10. // First clear any existing content.
  11. if (!shouldHydrate) { // 第一次render走这里
  12. let warned = false;
  13. let rootSibling;
  14. // 这里将container的子元素都清理掉了
  15. while ((rootSibling = container.lastChild)) {
  16. container.removeChild(rootSibling);
  17. }
  18. }
  19. // Legacy roots are not async by default.
  20. const isConcurrent = false;
  21. return new ReactRoot(container, isConcurrent, shouldHydrate);
  22. }

这个方法比较简单,在第一次调用ReactDOM.render时,shouldHydrate会是false,所以会走到if (!shouldHydrate) 分支里,将container节点的所有子节点都清理掉,最后是new 了一个ReactRoot作为返回值,关于ReactRoot等内容将放到后面的文章分析。

小结

以上是ReactDOM.render的函数调用示意图。

首先在render方法中校验container参数是否合法,然后调用legacyRenderSubtreeIntoContainer

在legacyRenderSubtreeIntoContainer中, 调用legacyCreateRootFromDOMContainer拿到了ReactRoot的一个实例,调用getPublicRootInstance拿到了instance,用于注入到callback,和作为返回值,调用unbatchedUpdates开始调度过程

在legacyCreateRootFromDOMContainer中,首先清理了container中的所有子节点,然后new了一个ReactRoot并返回

TODO

unbatchedUpdates是如何调度的

ReactRoot是一个什么样的类

dom的操作是在哪里

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/103511.html

相关文章

  • react解析: render的FiberRoot(三)

    摘要:查看创建核心函数源码行调用函数创建是相关,不用管源码行这个指的是调用创建,下面我们将会说到对象源码行源码行函数中,首先创建了一个,然后又创建了一个,它们两者还是相互引用。 感谢 yck: 剖析 React 源码解析,本篇文章是在读完他的文章的基础上,将他的文章进行拆解和加工,加入我自己的一下理解和例子,便于大家理解。觉得yck写的真的很棒 。React 版本为 16.8.6,关于源码的...

    muddyway 评论0 收藏0
  • React源码解析ReactDOM.render()

    摘要:一更新的方式有三种渲染接下来,我们就来看下源码二作用在提供的里渲染一个元素,并返回对该组件的引用常见的用法是这个官网网址源码服务端使用方法渲染节点是让服务端尽可能复用节点,提高性能元素容器应用渲染结束后,调用的函数错误抓取方法本质是返回 showImg(https://segmentfault.com/img/remote/1460000020064414?w=1240&h=641);...

    iKcamp 评论0 收藏0
  • React源码解析ReactDOM.render源码

    摘要:的创建组件,其实根源还是调用了编译之后一般写法建议用来进行源码的跟踪链接从源码角度来看创建一个组件的过程中发生了什么。 https://github.com/jimwmg/Rea... 1 React.createClass( ) var HelloWorld = React.createClass({ render : function(){ return ...

    joywek 评论0 收藏0
  • ReactRoot与ReactWork源码分析

    摘要:在中调用获得了的实例,然后调用其中的回调函数中调用了方法。这个类主要介绍其构造函数和方法,构造函数是时调用的,方法是的回调函数中使用的。这个将在下一篇分析。另外,方法是在的回调函数中调用的,也是一个参与后面调度的关键。 在ReactDOM.render源码解析-1中介绍了第一次render的基本过程的一部分,其中产生了ReactRoot和ReactWork两个类的实例。本文介绍下Rea...

    macg0406 评论0 收藏0
  • 剖析 React 源码render 流程(二)

    摘要:就是,如果你不了解这个的话可以阅读下相关文档,是应用初始化时就会生成的一个变量,值也是,并且这个值不会在后期再被改变。这是我的剖析 React 源码的第三篇文章,如果你没有阅读过之前的文章,请务必先阅读一下 第一篇文章 中提到的一些注意事项,能帮助你更好地阅读源码。 文章相关资料 React 16.8.6 源码中文注释,这个链接是文章的核心,文中的具体代码及代码行数都是依托于这个仓库 热身...

    My_Oh_My 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<