React 16 源码瞎几把解读 【三 点 一】 把react组件对象弄到dom中去(矛头指向fiber,fiber不解读这个过程也不知道)
一、ReactDOM.render 都干啥了
我们在写react的时候,最后一步肯定是
ReactDOM.render(
<div>
<Home name="home"/>
</div>
,
document.getElementById('app')
);
我们上面得知jsx被解析成了虚拟dom对象,我们把一个对象和一个dom传入render方法就得到了我们的页面,好神奇呀,我们开始撸到render方法:
const ReactDOM: Object = {
render(
element: React$Element<any>, // react组件对象
container: DOMContainer, // 就是id为app的那个dom
callback: ?Function, // callback 没有
) {
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
);
}
}
抛开typeScript那些恶心的类型限定不谈,我们发现render的实质就是调用并返回 legacyRenderSubtreeIntoContainer 这个函数执行后的结果,你看这个函数的命名:
legacy : 遗产 + render: 渲染 + subtree: 子树 + into: 到 + container: 容器
爱几把咋翻译咋翻译,大致意思就是说把 虚拟的dom树渲染到真实的dom容器中。此函数应当荣当 核心函数 宝座
二、legacyRenderSubtreeIntoContainer 又干了啥?
还是撸到丫的源码:
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>, // null
children: ReactNodeList, // element 虚拟dom树
container: DOMContainer, // html中的dom根对象
forceHydrate: boolean, // false 服务器端渲染标识
callback: ?Function, // 回调函数 没有
) {
// 对container进行校验
invariant(
isValidContainer(container),
'Target container is not a DOM element.',
);
// 取root对象,一般如果非服务器端渲染这个root是不存在的
let root: Root = (container._reactRootContainer: any);
// 进入浏览器端渲染流程
if (!root) {
// 人工制造root,附加了一堆fiber的东西到_reactRootContainer
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
); if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
// 该变callback的this为 instance
const instance = DOMRenderer.getPublicRootInstance(root._internalRoot);
originalCallback.call(instance);
};
}
DOMRenderer.unbatchedUpdates(() => {
if (parentComponent != null) {
// 向真实dom中挂载虚拟dom
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
// 多么直白的语义
root.render(children, callback);
}
});
} else {
// 还是先整一下callback
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = DOMRenderer.getPublicRootInstance(root._internalRoot);
originalCallback.call(instance);
};
}
// 还是上面那一套
if (parentComponent != null) {
// 向root中挂载dom
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
root.render(children, callback);
}
}
// 返回container 中的dom
return DOMRenderer.getPublicRootInstance(root._internalRoot);
}
通过看这个核心函数的代码,发现它其中有3个谜团:
1. _reactRootContainer 的制造:legacyCreateRootFromDOMContainer
这个函数会制造一个对象挂载到真实的dom根节点上,有了这个对象,执行该对象上的一些方法可以将虚拟dom变成dom树挂载到根节点上
2. DOMRenderer.unbatchedUpdates
它的回调执行了挂载dom结构的方法
3. root.legacy_renderSubtreeIntoContainer 和 root.render
如果有parentComponent 这个东西,就执行root.render 否则 root.legacy_renderSubtreeIntoContainer
三、跟进谜团
1.root的制造
找到 legacyCreateRootFromDOMContainer 函数:
function legacyCreateRootFromDOMContainer(
container: DOMContainer,
forceHydrate: boolean, // false
): Root {
const shouldHydrate =
forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
// 是否需要服务器端渲染
if (!shouldHydrate) {
let warned = false;
let rootSibling;
while ((rootSibling = container.lastChild)) {
if (__DEV__) {
...
}
// 将dom根节点清空
container.removeChild(rootSibling);
}
}
if (__DEV__) {
...
}
const isAsync = false;
return new ReactRoot(container, isAsync, shouldHydrate);
}
我们发现实际上该函数做的只是在非ssr的情况下,将dom根节点清空,然后返回一个new ReactRoot(...)
那么重点又跑到了ReactRoot中:
// 构造函数
function ReactRoot(
container: Container, // 被清空的dom根节点
isAsync: boolean, // false
hydrate: boolean // false
) {
// 追查之后发现:createFiberRoot(containerInfo, isAsync, hydrate);
// root 实际上就和fiber有了联系
const root = DOMRenderer.createContainer(container, isAsync, hydrate);
this._internalRoot = root;
} // 原型方法 // 渲染
ReactRoot.prototype.render = function(
children: ReactNodeList,
callback: ?() => mixed,
): Work {
const root = this._internalRoot;
const work = new ReactWork();
callback = callback === undefined ? null : callback; if (callback !== null) {
work.then(callback);
}
DOMRenderer.updateContainer(children, root, null, work._onCommit);
return work;
}; // 销毁组件
ReactRoot.prototype.unmount = function(callback: ?() => mixed): Work {
...
}; ReactRoot.prototype.legacy_renderSubtreeIntoContainer = function(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
callback: ?() => mixed,
): Work {
const root = this._internalRoot;
const work = new ReactWork();
callback = callback === undefined ? null : callback;
if (callback !== null) {
work.then(callback);
}
DOMRenderer.updateContainer(children, root, parentComponent, work._onCommit);
return work;
}; ReactRoot.prototype.createBatch = function(): Batch {
.....
};
通过以上代码我们就了解到root到底是个啥玩意儿,这个root有render等方法外,同时还附加了一个和fiber相关的 _internalRoot属性。
由此可知,不管是root.render 还是 root.legacy_renderSubtreeIntoContainer 都会去执行 DOMRenderer.updateContainer方法,无非就是传入的参数时:第三个参数传什么 而已。
2.DOMRenderer.unbatchedUpdates干了什么
// 正在批量更新标识
let isBatchingUpdates: boolean = false;
// 未批量更新标识
let isUnbatchingUpdates: boolean = false;
// 非批量更新操作
function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
// 如果正在批量更新
if (isBatchingUpdates && !isUnbatchingUpdates) {
// 未批量更新设为true
isUnbatchingUpdates = true;
try {
// 运行入参函数且返回执行结果
return fn(a);
} finally {
// 仍旧将未批量更新设为false
isUnbatchingUpdates = false;
}
}
// 不管是否在批量更新流程中,都执行入参函数
return fn(a);
}
记住这里两个十分重要的标识:isBatchingUpdates 和 isUnbatchingUpdates 初始值都是false
由此可知 unbatchedUpdates 无论如何都会执行入参函数,无非在批量更新的时候多一些流程控制。这里留坑
3. root.legacy_renderSubtreeIntoContainer 和 root.render 在弄什么?
通过上面的层层扒皮,无论怎样判断,最终都会到以上两个方法中,而这两个方法的核心就是 DOMRenderer.updateContainer,无非就是传不传父组件而已
传入的参数有: 1:虚拟dom对象树 2:之前造出来的root中和fiber相关的_internalRoot 3.父组件(null 或 父组件) 4.回调函数
export function updateContainer(
element: ReactNodeList, // 虚拟dom对象
container: OpaqueRoot, // 被制造出来的fiber root
parentComponent: ?React$Component<any, any>, // null
callback: ?Function, //没传
): ExpirationTime {
// 还记得虚拟dom对象
const current = container.current;
const currentTime = requestCurrentTime();
const expirationTime = computeExpirationForFiber(currentTime, current); // 计算优先级
return updateContainerAtExpirationTime(
element,
container,
parentComponent,
expirationTime,
callback,
);
}
// 剥皮 // 根据渲染优先级更新dom
export function updateContainerAtExpirationTime(
element: ReactNodeList, // 虚拟dom对象
container: OpaqueRoot, // 和fiber相关的_internalRoot
parentComponent: ?React$Component<any, any>, // 可有可无
expirationTime: ExpirationTime, // 计算出来的渲染优先级
callback: ?Function, // 回调函数,没有
) {
const current = container.current; if (__DEV__) {
...
}
// 获取到父组件内容
const context = getContextForSubtree(parentComponent);
// 赋值操作,不知道干啥用
if (container.context === null) {
container.context = context;
} else {
container.pendingContext = context;
}
// 又到了下一站:schedule:安排, Root: 根, Update:更新
return scheduleRootUpdate(current, element, expirationTime, callback);
}
// 剥皮 // 安排根节点更新
function scheduleRootUpdate(
current: Fiber, // fiber对象
element: ReactNodeList, // 虚拟dom树
expirationTime: ExpirationTime, // 更新优先级
callback: ?Function, // 回调
) {
if (__DEV__) {
...
}
/*
export const UpdateState = 0;
export function createUpdate(expirationTime: ExpirationTime): Update<*> {
return {
expirationTime: expirationTime, tag: UpdateState,
payload: null,
callback: null, next: null,
nextEffect: null,
};
}
*/ // 返回一个包含以上属性的update对象
const update = createUpdate(expirationTime);
// 将虚拟dom树放入payload
update.payload = {element}; callback = callback === undefined ? null : callback;
if (callback !== null) {
warningWithoutStack(
typeof callback === 'function',
'render(...): Expected the last optional `callback` argument to be a ' +
'function. Instead received: %s.',
callback,
);
update.callback = callback;
}
// 开始加入更新队列了,又得剥皮
enqueueUpdate(current, update);
//
scheduleWork(current, expirationTime);
return expirationTime;
} // 更新队列
// 核心update
export function enqueueUpdate<State>(
fiber: Fiber,
update: Update<State> // 上文那个update对象
) {
// 根据fiber的指示进行更新
...
}
根据一系列的剥皮,最终指向了enqueueUpdate 这个函数,而这个函数和fiber是紧密耦合的,fiber是一个棘手的问题,不理解fiber就无法弄清虚拟dom如何更新到真实dom中。
预知fiber如何,且听后续分晓!!!
四、本次的坑有以下几个:
1. _internalRoot 如何制造出来的,丫有什么作用,为什么后面的函数参数都传它
2. enqueueUpdate 跟fiber的关系还不清不楚
3. expirationTime 是干什么的,它的这个优先级有什么用呢?
下期解答!
React 16 源码瞎几把解读 【三 点 一】 把react组件对象弄到dom中去(矛头指向fiber,fiber不解读这个过程也不知道)的更多相关文章
- React 16 源码瞎几把解读 【二】 react组件的解析过程
一.一个真正的react组件编译后长啥样? 我们瞎几把解读了react 虚拟dom对象是怎么生成的,生成了一个什么样的解构.一个react组件不光由若干个这些嵌套的虚拟dom对象组成,还包括各种生命周 ...
- React 16 源码瞎几把解读 【一】 从jsx到一个react 虚拟dom对象
一.jsx变createElement 每一个用jsx语法书写的react组件最后都会变成 react.createElement(...)这一坨东西, // 转变前 export default ( ...
- React 16 源码瞎几把解读 【前戏】 为啥组件外面非得包个标签?
〇.看前准备 1.自行clone react最新代码 2.自行搭建一个能跑react的test项目 一.看表面:那些插件 如何解析JSX 有如下一段代码: // ---- hearder.jsx 组件 ...
- React 16 源码瞎几把解读 【三 点 二】 react中的fiberRoot
〇.先来看看常用的常量 NoWork = 0 noTimeout = undefined HostRoot = 3 NoContext = 0b000; AsyncMode = 0b001; Stri ...
- 《React Native 精解与实战》书籍连载「React Native 源码学习方法及其他资源」
此系列文章将整合我的 React 视频教程与 React Native 书籍中的精华部分,给大家介绍 React Native 源码学习方法及其他资源. 最后的章节给大家介绍 React Native ...
- React的React.createContext()源码解析(四)
一.产生context原因 从父组件直接传值到孙子组件,而不必一层一层的通过props进行传值,相比较以前的那种传值更加的方便.简介. 二.context的两种实现方式 1.老版本(React16.x ...
- React的React.createElement源码解析(一)
一.什么是jsx jsx是语法糖 它是js和html的组合使用 二.为什么用jsx语法 高效定义模版,编译后使用 不会带来性能问题 三.jsx语法转化为js语法 jsx语法通过babel转化为 ...
- 从微信小程序开发者工具源码看实现原理(三)- - 双线程通信
文章概览: 引言 小程序开发者工具双线程通信的设计 1.on: 用来收集小程序开发者工具触发的事件回调 2.invoke:以api方式调用开发工具提供的基础能力 3.publish:用来向Appser ...
- 菜鸟nginx源码剖析数据结构篇(三) 单向链表 ngx_list_t[转]
菜鸟nginx源码剖析数据结构篇(三) 单向链表 ngx_list_t Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.csd ...
随机推荐
- js function的方法名是一个变量 能被重复定义 当变量名一致时候 会使用最后一个function
- 【bzoj2656】[Zjoi2012]数列(sequence) 高精度
题目描述 给出数列 $A$ 的递推公式如下图所示,$T$ 次给定 $n$ ,求 $A_n$ . 输入 输入文件第一行有且只有一个正整数T,表示测试数据的组数.第2-T+1行,每行一个非负整数N. 输出 ...
- 【bzoj4372】烁烁的游戏 动态点分治+线段树
题目描述 给一颗n个节点的树,边权均为1,初始点权均为0,m次操作:Q x:询问x的点权.M x d w:将树上与节点x距离不超过d的节点的点权均加上w. 输入 第一行两个正整数:n,m接下来的n-1 ...
- 洛谷 P2195 HXY造公园 解题报告
P2195 HXY造公园 题目描述 现在有一个现成的公园,有\(n\)个休息点和\(m\)条双向边连接两个休息点.众所周知,\(HXY\)是一个\(SXBK\)的强迫症患者,所以她打算施展魔法来改造公 ...
- poj3421 X-factor Chains
X-factor Chains Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 7733 Accepted: 2447 D ...
- [ldap]ldap server安装以及图形化操作
https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-a-basic-ldap-server-on ...
- 前端PHP入门-028-文件操作-掌握级别
作为刚入门我们员经常会干的意见事情是ctrl+c和ctrl+v,鼠标右键删除文件.会control+c(或右键)复制.粘贴文件以及新建文件,还可以设置文件的是否为只读文件等等 可不可以写入修改配置文件 ...
- [线索二叉树] [LeetCode] 不需要栈或者别的辅助空间,完成二叉树的中序遍历。题:Recover Binary Search Tree,Binary Tree Inorder Traversal
既上篇关于二叉搜索树的文章后,这篇文章介绍一种针对二叉树的新的中序遍历方式,它的特点是不需要递归或者使用栈,而是纯粹使用循环的方式,完成中序遍历. 线索二叉树介绍 首先我们引入“线索二叉树”的概念: ...
- [LeetCode] 数学计算模拟类问题:加法,除法和幂,注意越界问题。题 剑指Offer,Pow(x, n) ,Divide Two Integers
引言 数学计算的模拟类题目,往往是要求实现某种计算(比如两数相除),实现的过程中会有所限定,比如不允许乘法等等. 这类题目首先要注意计算过程中本身的特殊情况.比如求相除,则必须首先反映过来除数不能为0 ...
- redis-cluster 集群搭建详细指南及常见问题集合
只当个搬运工吧 搭建篇:https://www.cnblogs.com/mafly/p/redis_cluster.html 测试能用 常见问题: 1 redis操作key时出现以下错误 (erro ...