text: (window.webpackJsonp=window.webpackJsonp||[]).push([[57],{390:function(t,a,s){"use strict";s.r(a);var e=s(2),n=Object(e.a)({},(function(){var t=this,a=t.$createElement,s=t._self._c||a;return s("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[s("h1",{attrs:{id:"剖析-react-源码"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#剖析-react-源码"}},[t._v("#")]),t._v(" 剖析 React 源码")]),t._v(" "),s("ul",[s("li",[t._v("市面上 Vue 的源码解读数不胜数，但是反观 React 相关的却寥寥无几，也是因为 React 源码难度较高，因此我想来攻克这个难题")]),t._v(" "),s("li",[t._v("自己觉得看懂并不一定看懂了，写出来让读者看懂才是真懂了，因此我要把我读懂的东西写出来")])]),t._v(" "),s("p",[t._v("，"),s("strong",[t._v("React 版本为 16.8.6")]),t._v("，以下是本系列文章你必须需要注意的地方：")]),t._v(" "),s("ul",[s("li",[t._v("这是一门进阶课，如果涉及到你不清楚的内容，请自行谷歌，另外最好具备 React 的开发能力")]),t._v(" "),s("li",[t._v("这是一门讲源码的课，只阅读是不大可能真正读懂的，需要辅以 Demo 和 Debug 才能真正理解代码的")])]),t._v(" "),s("p",[s("img",{attrs:{src:"https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-032353.png",alt:""}})]),t._v(" "),s("div",{staticClass:"language-! line-numbers-mode"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[t._v("开始进入正文前先说下这个系列中我的行文思路：1. 代码尽量通过图片展示，既美观又方便阅读，反正不需要大家复制代码。2. 文章中只会讲我认为重要或者有意思的代码，对于其他代码请自行阅读我的仓库，反正已经注释好代码了。3. 对于流程长的函数调用会使用流程图的方式来总结。4. 不会干巴巴的只讲代码，会结合实际来聊聊这些 API 能帮助我们解决什么问题。\n")])]),t._v(" "),s("div",{staticClass:"line-numbers-wrapper"},[s("span",{staticClass:"line-number"},[t._v("1")]),s("br")])]),s("h2",{attrs:{id:"文章相关资料"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#文章相关资料"}},[t._v("#")]),t._v(" 文章相关资料")]),t._v(" "),s("ul",[s("li",[s("a",{attrs:{href:"https://github.com/KieSun/react-interpretation",target:"_blank",rel:"noopener noreferrer"}},[t._v("React 16.8.6 源码中文注释"),s("OutboundLink")],1),t._v("，文中的具体代码及代码行数都是依托于这个仓库")]),t._v(" "),s("li",[s("router-link",{attrs:{to:"/md/frame/React/render-1.html"}},[t._v("render 流程（一）")])],1),t._v(" "),s("li",[s("router-link",{attrs:{to:"/md/frame/React/render-2.html"}},[t._v("render 流程（二）")])],1)]),t._v(" "),s("p",[s("img",{attrs:{src:"https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-032355.png",alt:""}})]),t._v(" "),s("h2",{attrs:{id:"react-createelement"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#react-createelement"}},[t._v("#")]),t._v(" React.createElement")]),t._v(" "),s("p",[t._v("大家在写 React 代码的时候肯定写过 JSX，但是为什么一旦使用 JSX 就必须引入 React 呢？")]),t._v(" "),s("p",[t._v("这是因为我们的 JSX 代码会被 Babel 编译为 "),s("code",[t._v("React.createElement")]),t._v("，不引入 React 的话就不能使用 "),s("code",[t._v("React.createElement")]),t._v(" 了。")]),t._v(" "),s("div",{staticClass:"language-js line-numbers-mode"},[s("pre",{pre:!0,attrs:{class:"language-js"}},[s("code",[s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("<")]),t._v("div id"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[t._v("'1'")]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v(">")]),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("1")]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("<")]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("/")]),t._v("div"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v(">")]),t._v("\n"),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 上面的 JSX 会被编译成这样")]),t._v("\nReact"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("createElement")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"div"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n\tid"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"1"')]),t._v("\n"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"1"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n")])]),t._v(" "),s("div",{staticClass:"line-numbers-wrapper"},[s("span",{staticClass:"line-number"},[t._v("1")]),s("br"),s("span",{staticClass:"line-number"},[t._v("2")]),s("br"),s("span",{staticClass:"line-number"},[t._v("3")]),s("br"),s("span",{staticClass:"line-number"},[t._v("4")]),s("br"),s("span",{staticClass:"line-number"},[t._v("5")]),s("br")])]),s("p",[t._v("那么我们就先定位到 "),s("strong",[t._v("ReactElement.js")]),t._v(" 文件阅读下 "),s("code",[t._v("createElement")]),t._v(" 函数的实现")]),t._v(" "),s("div",{staticClass:"language-js line-numbers-mode"},[s("pre",{pre:!0,attrs:{class:"language-js"}},[s("code",[s("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("export")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("function")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("createElement")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),s("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("type"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" config"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" children")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),s("div",{staticClass:"line-numbers-wrapper"},[s("span",{staticClass:"line-number"},[t._v("1")]),s("br")])]),s("p",[t._v("首先 "),s("code",[t._v("createElement")]),t._v(" 函数接收三个参数，具体代表着什么相信大家可以通过上面 JSX 编译出来的东西自行理解。")]),t._v(" "),s("p",[t._v("然后是对于 "),s("code",[t._v("config")]),t._v(" 的一些处理：")]),t._v(" "),s("p",[s("img",{attrs:{src:"https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-032356.png",alt:""}})]),t._v(" "),s("p",[t._v("这段代码对 "),s("code",[t._v("ref")]),t._v(" 以及 "),s("code",[t._v("key")]),t._v(" 做了个验证（对于这种代码就无须阅读内部实现，通过函数名就可以了解它想做的事情），然后遍历 "),s("code",[t._v("config")]),t._v(" 并把内建的几个属性（比如 "),s("code",[t._v("ref")]),t._v(" 和 "),s("code",[t._v("key")]),t._v("）剔除后丢到 props 对象中。")]),t._v(" "),s("p",[t._v("接下里是一段对于 "),s("code",[t._v("children")]),t._v(" 的操作")]),t._v(" "),s("p",[s("img",{attrs:{src:"https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-032358.png",alt:""}})]),t._v(" "),s("p",[t._v("首先把第二个参数之后的参数取出来，然后判断长度是否大于一。大于一的话就代表有多个 "),s("code",[t._v("children")]),t._v("，这时候 "),s("code",[t._v("props.children")]),t._v(" 会是一个数组，否则的话只是一个对象。"),s("strong",[t._v("因此我们需要注意在对 "),s("code",[t._v("props.children")]),t._v(" 进行遍历的时候要注意它是否是数组")]),t._v("，当然你也可以利用 "),s("code",[t._v("React.Children")]),t._v(" 中的 API，下文中也会对 "),s("code",[t._v("React.Children")]),t._v(" 中的 API 进行讲解。")]),t._v(" "),s("p",[t._v("最后就是返回了一个 "),s("code",[t._v("ReactElement")]),t._v(" 对象")]),t._v(" "),s("p",[s("img",{attrs:{src:"https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-032359.png",alt:""}})]),t._v(" "),s("p",[t._v("内部代码很简单，"),s("strong",[t._v("核心")]),t._v("就是通过 "),s("code",[t._v("$$typeof")]),t._v(" 来帮助我们识别这是一个 "),s("code",[t._v("ReactElement")]),t._v("，后面我们可以看到很多这样类似的类型。另外我们需要注意一点的是：通过 JSX写的 "),s("code",[t._v("<APP />")]),t._v(" 代表着 "),s("code",[t._v("ReactElement")]),t._v("，"),s("code",[t._v("APP")]),t._v(" 代表着 React Component。")]),t._v(" "),s("p",[t._v("以下是这一小节的流程图内容：")]),t._v(" "),s("p",[s("img",{attrs:{src:"https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-032401.png",alt:""}})]),t._v(" "),s("h2",{attrs:{id:"reactbaseclasses"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#reactbaseclasses"}},[t._v("#")]),t._v(" ReactBaseClasses")]),t._v(" "),s("p",[t._v("上文中讲到了 "),s("code",[t._v("APP")]),t._v(" 代表着 React Component，那么这一小节我们就来阅读组件相关也就是 "),s("strong",[t._v("ReactBaseClasses.js")]),t._v(" 文件下的代码。")]),t._v(" "),s("p",[t._v("其实在阅读这部分源码之前，我以为代码会很复杂，可能包含了很多组件内的逻辑，结果内部代码相当简单。这是因为 React 团队将复杂的逻辑全部丢在了 react-dom 文件夹中，你可以把 react-dom 看成是 React 和 UI 之间的"),s("strong",[t._v("胶水层")]),t._v("，这层胶水可以兼容很多平台，比如 Web、RN、SSR 等等。")]),t._v(" "),s("p",[t._v("该文件包含两个基本组件，分别为 "),s("code",[t._v("Component")]),t._v(" 及 "),s("code",[t._v("PureComponent")]),t._v("，我们先来阅读 "),s("code",[t._v("Component")]),t._v(" 这部分的代码。")]),t._v(" "),s("p",[s("img",{attrs:{src:"https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-032402.png",alt:""}})]),t._v(" "),s("p",[t._v("构造函数 "),s("code",[t._v("Component")]),t._v(" 中需要注意的两点分别是 "),s("code",[t._v("refs")]),t._v(" 和 "),s("code",[t._v("updater")]),t._v("，前者会在下文中专门介绍，后者是组件中相当重要的一个属性，我们可以发现 "),s("code",[t._v("setState")]),t._v(" 和 "),s("code",[t._v("forceUpdate")]),t._v(" 都是调用了 "),s("code",[t._v("updater")]),t._v(" 中的方法，但是 "),s("code",[t._v("updater")]),t._v(" 是 react-dom 中的内容，我们会在之后的文章中学习到这部分的内容。")]),t._v(" "),s("p",[t._v("另外 "),s("code",[t._v("ReactNoopUpdateQueue")]),t._v(" 也有一个单独的文件，但是内部的代码看不看都无所谓，因为都是用于报警告的。")]),t._v(" "),s("p",[t._v("接下来我们来阅读 "),s("code",[t._v("PureComponent")]),t._v(" 中的代码，其实这部分的代码基本与 "),s("code",[t._v("Component")]),t._v(" 一致")]),t._v(" "),s("p",[s("img",{attrs:{src:"https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-032404.png",alt:""}})]),t._v(" "),s("p",[s("code",[t._v("PureComponent")]),t._v(" 继承自 "),s("code",[t._v("Component")]),t._v("，继承方法使用了很典型的寄生组合式。")]),t._v(" "),s("p",[t._v("另外这两部分代码你可以发现每个组件都有一个 "),s("code",[t._v("isXXXX")]),t._v(" 属性用来标志自身属于什么组件。")]),t._v(" "),s("p",[t._v("以上就是这部分的代码，接下来的一小节我们将会学习到 "),s("code",[t._v("refs")]),t._v(" 的一部分内容。")]),t._v(" "),s("h2",{attrs:{id:"refs"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#refs"}},[t._v("#")]),t._v(" Refs")]),t._v(" "),s("p",[t._v("refs 其实有好几种方式可以创建：")]),t._v(" "),s("ul",[s("li",[t._v("字符串的方式，但是这种方式已经不推荐使用")]),t._v(" "),s("li",[s("code",[t._v("ref={el => this.el = el}")])]),t._v(" "),s("li",[s("code",[t._v("React.createRef")])])]),t._v(" "),s("p",[t._v("这一小节我们来学习 "),s("code",[t._v("React.createRef")]),t._v(" 相关的内容，其余的两种方式不在这篇文章的讨论范围之内，请先定位到 "),s("strong",[t._v("ReactCreateRef.js")]),t._v(" 文件。")]),t._v(" "),s("p",[s("img",{attrs:{src:"https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-032405.png",alt:""}})]),t._v(" "),s("p",[t._v("内部实现很简单，如果我们想使用 "),s("code",[t._v("ref")]),t._v("，只需要取出其中的 "),s("code",[t._v("current")]),t._v(" 对象即可。")]),t._v(" "),s("p",[t._v("另外对于函数组件来说，是不能使用 "),s("code",[t._v("ref")]),t._v(" 的，如果你不知道原因的话可以直接阅读 "),s("a",{attrs:{href:"https://react.docschina.org/docs/refs-and-the-dom.html#refs-%E4%B8%8E%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BB%84%E4%BB%B6",target:"_blank",rel:"noopener noreferrer"}},[t._v("文档"),s("OutboundLink")],1),t._v("。")]),t._v(" "),s("p",[t._v("当然在之前也是有取巧的方式的，就是通过 "),s("code",[t._v("props")]),t._v(" 的方式传递 "),s("code",[t._v("ref")]),t._v("，但是现在我们有了新的方式 "),s("code",[t._v("forwardRef")]),t._v(" 去解决这个问题。")]),t._v(" "),s("p",[t._v("具体代码见 "),s("strong",[t._v("forwardRef.js")]),t._v(" 文件，同样内部代码还是很简单")]),t._v(" "),s("p",[s("img",{attrs:{src:"https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-032407.png",alt:""}})]),t._v(" "),s("p",[t._v("这部分代码最重要的就是我们可以在参数中获得 "),s("code",[t._v("ref")]),t._v(" 了，因此我们如果想在函数组件中使用 "),s("code",[t._v("ref")]),t._v(" 的话就可以把代码写成这样：")]),t._v(" "),s("div",{staticClass:"language-js line-numbers-mode"},[s("pre",{pre:!0,attrs:{class:"language-js"}},[s("code",[s("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" FancyButton "),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" React"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("forwardRef")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),s("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("props"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" ref")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("\n\t"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("<")]),t._v("button ref"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("ref"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" className"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"FancyButton"')]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v(">")]),t._v("\n\t\t"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("props"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("children"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n\t"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("<")]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("/")]),t._v("button"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v(">")]),t._v("\n"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n")])]),t._v(" "),s("div",{staticClass:"line-numbers-wrapper"},[s("span",{staticClass:"line-number"},[t._v("1")]),s("br"),s("span",{staticClass:"line-number"},[t._v("2")]),s("br"),s("span",{staticClass:"line-number"},[t._v("3")]),s("br"),s("span",{staticClass:"line-number"},[t._v("4")]),s("br"),s("span",{staticClass:"line-number"},[t._v("5")]),s("br")])]),s("h2",{attrs:{id:"reactchildren"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#reactchildren"}},[t._v("#")]),t._v(" ReactChildren")]),t._v(" "),s("p",[t._v("这一小节会是这篇文章中最复杂的一部分，可能需要自己写个 Demo 并且 Debug 一下才能真正理解源码为什么要这样实现。")]),t._v(" "),s("p",[t._v("首先大家需要定位到 "),s("strong",[t._v("ReactChildren.js")]),t._v(" 文件，这部分代码中我只会介绍关于 "),s("code",[t._v("mapChildren")]),t._v(" 函数相关的内容，因为这部分代码基本就贯穿了整个文件了。")]),t._v(" "),s("p",[t._v("如果你没有使用过这个 API，可以先自行阅读 "),s("a",{attrs:{href:"https://reactjs.org/docs/react-api.html#reactchildren",target:"_blank",rel:"noopener noreferrer"}},[t._v("文档"),s("OutboundLink")],1),t._v("。")]),t._v(" "),s("p",[t._v("对于 "),s("code",[t._v("mapChildren")]),t._v(" 这个函数来说，通常会使用在组合组件设计模式上。如果你不清楚什么是组合组件的话，可以看下 Ant-design，它内部大量使用了这种设计模式，比如说 "),s("code",[t._v("Radio.Group")]),t._v("、"),s("code",[t._v("Radio.Button")]),t._v("，另外这里也有篇 "),s("a",{attrs:{href:"https://react-cn.github.io/react/docs/multiple-components.html",target:"_blank",rel:"noopener noreferrer"}},[t._v("文档"),s("OutboundLink")],1),t._v(" 介绍了这种设计模式。")]),t._v(" "),s("p",[t._v("我们先来看下这个函数的一些神奇用法")]),t._v(" "),s("div",{staticClass:"language-js line-numbers-mode"},[s("pre",{pre:!0,attrs:{class:"language-js"}},[s("code",[t._v("React"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("Children"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("map")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),s("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("props"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("children"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("c")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v("c"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" c"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n")])]),t._v(" "),s("div",{staticClass:"line-numbers-wrapper"},[s("span",{staticClass:"line-number"},[t._v("1")]),s("br")])]),s("p",[t._v("对于上述代码，"),s("code",[t._v("map")]),t._v(" 也就是 "),s("code",[t._v("mapChildren")]),t._v(" 函数来说返回值是 "),s("code",[t._v("[c, c, c, c]")]),t._v("。不管你第二个参数的函数返回值是几维嵌套数组，"),s("code",[t._v("map")]),t._v(" 函数都能帮你摊平到一维数组，并且每次遍历后返回的数组中的元素个数代表了同一个节点需要复制几次。")]),t._v(" "),s("p",[t._v("如果文字描述有点难懂的话，就来看代码吧：")]),t._v(" "),s("div",{staticClass:"language-html line-numbers-mode"},[s("pre",{pre:!0,attrs:{class:"language-html"}},[s("code",[s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("<")]),t._v("div")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(">")])]),t._v("\n\t\t"),s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("<")]),t._v("span")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(">")])]),t._v("1"),s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("</")]),t._v("span")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(">")])]),t._v("\n\t\t"),s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("<")]),t._v("span")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(">")])]),t._v("2"),s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("</")]),t._v("span")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(">")])]),t._v("\n"),s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("</")]),t._v("div")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(">")])]),t._v("\n")])]),t._v(" "),s("div",{staticClass:"line-numbers-wrapper"},[s("span",{staticClass:"line-number"},[t._v("1")]),s("br"),s("span",{staticClass:"line-number"},[t._v("2")]),s("br"),s("span",{staticClass:"line-number"},[t._v("3")]),s("br"),s("span",{staticClass:"line-number"},[t._v("4")]),s("br")])]),s("p",[t._v("对于上述代码来说，通过 "),s("code",[t._v("c => [[c, c]]")]),t._v(" 转换以后就变成了")]),t._v(" "),s("div",{staticClass:"language-html line-numbers-mode"},[s("pre",{pre:!0,attrs:{class:"language-html"}},[s("code",[s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("<")]),t._v("span")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(">")])]),t._v("1"),s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("</")]),t._v("span")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(">")])]),t._v("\n"),s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("<")]),t._v("span")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(">")])]),t._v("1"),s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("</")]),t._v("span")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(">")])]),t._v("\n"),s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("<")]),t._v("span")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(">")])]),t._v("2"),s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("</")]),t._v("span")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(">")])]),t._v("\n"),s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("<")]),t._v("span")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(">")])]),t._v("2"),s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token tag"}},[s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("</")]),t._v("span")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(">")])]),t._v("\n")])]),t._v(" "),s("div",{staticClass:"line-numbers-wrapper"},[s("span",{staticClass:"line-number"},[t._v("1")]),s("br"),s("span",{staticClass:"line-number"},[t._v("2")]),s("br"),s("span",{staticClass:"line-number"},[t._v("3")]),s("br"),s("span",{staticClass:"line-number"},[t._v("4")]),s("br")])]),s("p",[t._v("接下里我们进入正题，来看看 "),s("code",[t._v("mapChildren")]),t._v(" 内部到底是如何实现的。")]),t._v(" "),s("p",[s("img",{attrs:{src:"https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-032408.png",alt:""}})]),t._v(" "),s("p",[t._v("这段代码有意思的部分是引入了对象重用池的概念，分别对应 "),s("code",[t._v("getPooledTraverseContext")]),t._v(" 和 "),s("code",[t._v("releaseTraverseContext")]),t._v(" 中的代码。当然这个概念的用处其实很简单，就是维护一个大小固定的对象重用池，每次从这个池子里取一个对象去赋值，用完了就将对象上的属性置空然后丢回池子。维护这个池子的用意就是提高性能，毕竟频繁创建销毁一个有很多属性的对象会消耗性能。")]),t._v(" "),s("p",[t._v("接下来我们来学习 "),s("code",[t._v("traverseAllChildrenImpl")]),t._v(" 中的代码，这部分的代码需要分为两块来讲")]),t._v(" "),s("p",[s("img",{attrs:{src:"https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-032411.png",alt:""}})]),t._v(" "),s("p",[t._v("这部分的代码相对来说简单点，主体就是在判断 "),s("code",[t._v("children")]),t._v(" 的类型是什么。如果是可以渲染的节点的话，就直接调用 "),s("code",[t._v("callback")]),t._v("，另外你还可以发现在判断的过程中，代码中有使用到 "),s("code",[t._v("$$typeof")]),t._v(" 去判断的流程。这里的 "),s("code",[t._v("callback")]),t._v(" 指的是 "),s("code",[t._v("mapSingleChildIntoContext")]),t._v(" 函数，这部分的内容会在下文中说到。")]),t._v(" "),s("p",[s("img",{attrs:{src:"https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-032412.png",alt:""}})]),t._v(" "),s("p",[t._v("这部分的代码首先会判断 "),s("code",[t._v("children")]),t._v(" 是否为数组。如果为数组的话，就遍历数组并把其中的每个元素都递归调用 "),s("code",[t._v("traverseAllChildrenImpl")]),t._v("，也就是说必须是单个可渲染节点才可以执行上半部分代码中的 "),s("code",[t._v("callback")]),t._v("。")]),t._v(" "),s("p",[t._v("如果不是数组的话，就看看 "),s("code",[t._v("children")]),t._v(" 是否可以支持迭代，原理就是通过 "),s("code",[t._v("obj[Symbol.iterator]")]),t._v(" 的方式去取迭代器，返回值如果是个函数的话就代表支持迭代，然后逻辑就和之前的一样了。")]),t._v(" "),s("p",[t._v("讲完了 "),s("code",[t._v("traverseAllChildrenImpl")]),t._v(" 函数，我们最后再来阅读下 "),s("code",[t._v("mapSingleChildIntoContext")]),t._v(" 函数中的实现。")]),t._v(" "),s("p",[s("img",{attrs:{src:"https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-032414.png",alt:""}})]),t._v(" "),s("p",[s("code",[t._v("bookKeeping")]),t._v(" 就是我们从对象池子里取出来的东西，然后调用 "),s("code",[t._v("func")]),t._v(" 并且传入节点（此时这个节点肯定是单个节点），此时的 "),s("code",[t._v("func")]),t._v(" 代表着 "),s("code",[t._v("React.mapChildren")]),t._v(" 中的第二个参数。")]),t._v(" "),s("p",[t._v("接下来就是判断返回值类型的过程：如果是数组的话，还是回归之前的代码逻辑，注意这里传入的 "),s("code",[t._v("func")]),t._v(" 是 "),s("code",[t._v("c => c")]),t._v("，因为要保证最终结果是被摊平的；如果不是数组的话，判断返回值是否是一个有效的 Element，验证通过的话就 clone 一份并且替换掉 "),s("code",[t._v("key")]),t._v("，最后把返回值放入 "),s("code",[t._v("result")]),t._v(" 中，"),s("code",[t._v("result")]),t._v(" 其实也就是 "),s("code",[t._v("mapChildren")]),t._v(" 的返回值。")]),t._v(" "),s("p",[t._v("至此，"),s("code",[t._v("mapChildren")]),t._v(" 函数相关的内容已经解析完毕，还不怎么清楚的同学可以通过以下的流程图再复习一遍。")]),t._v(" "),s("p",[s("img",{attrs:{src:"https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-032416.png",alt:""}})]),t._v(" "),s("h2",{attrs:{id:"其余内容"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#其余内容"}},[t._v("#")]),t._v(" 其余内容")]),t._v(" "),s("p",[t._v("前面几小节的内容已经把 react 文件夹下大部分有意思的代码都讲完了，其他就剩余了一些边边角角的内容。比如 "),s("code",[t._v("memo")]),t._v("、"),s("code",[t._v("context")]),t._v("、"),s("code",[t._v("hooks")]),t._v("、"),s("code",[t._v("lazy")]),t._v("，这部分代码有兴趣的可以直接自行阅读，反正内容都还是很简单的，难的部分都在 react-dom 文件夹中。")])])}),[],!1,null,null,null);a.default=n.exports}}]);