You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
graph TD
A[jsx] -->|createElement| B(virtual-DOM)
B -->|mountComponent| C(真实DOM)
C --> D(state/props发生改变)
D --> E(重新生成V-DOM)
E --> F{diff算法}
F -->|节点类型不同| G[删旧建新]
F -->|节点类型相同| H[DOM/React]
H -->|DOM元素| I[更新属性]
H -->|React组件| J[更新状态]
有了虚拟 dom,接下来的工作就是把这个虚拟 dom 树真正渲染成一个 dom 树。React 的做法是针对不同的 type 构造相应的渲染对象,渲染对象提供一个 mountComponent 方法(负责把对应的某个虚拟 dom 的节点生成具体的 dom node),然后循环迭代整个 vdom tree 生成一个完整的 dom node tree,最终插入容器节点;
当参数为函数时,React会将所有更新组成队列,并且按顺序来执行,这样避免了将state合并成一个对象的问题,之后会启动一个reconciliation调和过程,即创建一个新的 React Element tree(UI层面的对象表示)并且和之前的tree作比较,基于你传递给setState的对象找出发生的变化,最后更新DOM中需改动的部分。
[TOC]
印象笔记转过来的,部分图片丢失
生命周期
提到 React 就必需会的生命周期
很全面 - React生命周期管理
渲染过程
虚拟DOM生成真实DOM的过程
虚拟的DOM的核心思想是:对复杂的文档DOM结构,提供一种方便的工具,进行最小化地DOM操作。
DOM很慢,而javascript很快,用javascript对象可以很容易地表示DOM节点。DOM节点包括标签、属性和子节点,通过VElement表示如下。
现在ul只是一个 JavaScript 对象表示的 DOM 结构,页面上并没有这个结构。我们可以根据这个ul构建真正的
:
- Virtual DOM 在牺牲(牺牲很关键)部分性能的前提下,增加了可维护性,这也是很多框架的通性。
- 实现了对 DOM 的集中化操作,在数据改变时先对虚拟 DOM 进行修改,再反映到真实的 DOM 中,用最小的代价来更新 DOM,提升效率(要想想是跟哪个阶段相比提升了效率,别只记住了这一条)
- 打开了函数式 UI 编程的大门
- 可以渲染到 DOM 以外的端,使得框架跨平台,比如 ReactNative,React VR 等
- 可以更好地实现 SSR、同构渲染等。这条其实是跟上面一条差不多的
- 组件的高度抽象化
-
-
-
- 尽量少的创建 / 删除节点,多使用移动节点的方式
- 比较次数要尽量少,算法要足够的快
- 如果 2 个节点的类型不一样,就认为以这 2 个节点为根结点的树会完全不同
- 对于多次 render 中结构保持不变的节点,开发者会用一个 key 属性标识出来,以便重用
nextIndex
lastIndex
_mountIndex
遍历 nextChildren 时候的 index,每遍历一个元素加 1
上一次从 prevChildren 中取出来元素时,这个元素在 prevChildren 中的 index
元素在数组中的位置
- nextChildren 的第一个元素是 B ,在 prevChildren 中的位置是 1(_mountIndex),nextIndex 和 lastIndex 都是 0。节点移动的前提是_mountIndex < lastIndex,因此 B 不需要移动。lastIndex 更新为 _mountIndex 和 lastIndex 中较大的:1 。nextIndex 自增:1;
- nextChildren 的第二个元素是 A ,在 prevChildren 中的位置是 0(_mountIndex),nextIndex 和 lastIndex 都是 1。这时_mountIndex < lastIndex,因此将 A 移动到 lastPlacedNode (B)的后面 。lastIndex 更新为 _mountIndex 和 lastIndex 中较大的:1 。nextIndex 自增:2;
- nextChildren 的第三个元素是 D ,中 prevChildren 中的位置是 3(_mountIndex),nextIndex 是 2 ,lastIndex 是 1。这时不满足_mountIndex < lastIndex,因此 D 不需要移动。lastIndex 更新为 _mountIndex 和 lastIndex 中较大的:3 。nextIndex 自增:3;
- nextChildren 的第四个元素是 C ,中 prevChildren 中的位置是 2(_mountIndex),nextIndex 是 3 ,lastIndex 是 3。这时_mountIndex < lastIndex,因此将 C 移动到 lastPlacedNode (D)的后面。循环结束。
- Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计。
- 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。
- 对于同一层级的一组子节点,它们可以通过唯一 key 进行区分。
- 如果是同一类型的组件,按照原策略继续比较 virtual DOM tree。
- 如果不是,则将该组件判断为 dirty component,从而替换整个组件下的所有子节点。
- 对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切的知道这点那可以节省大量的 diff 运算时间,因此 React 允许用户通过 shouldComponentUpdate() 来判断该组件是否需要进行 diff。
- element相同,原地复用
- element 不同,删旧建新
- 当对象作为参数执行setState时,React内部会以一种对象合并的方式来批量更新组件的状态,类似于Object.assign(),把需要更新的state合并后放入状态队列,利用这个队列可以更加高效的批量更新state;
- 当参数为函数时,React会将所有更新组成队列,并且按顺序来执行,这样避免了将state合并成一个对象的问题,之后会启动一个reconciliation调和过程,即创建一个新的 React Element tree(UI层面的对象表示)并且和之前的tree作比较,基于你传递给setState的对象找出发生的变化,最后更新DOM中需改动的部分。
- setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的。
- setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
- setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。
- ES6 中,在子类的 constructor 中必须先调用 super 才能引用 this;
- super(props)的目的:在constructor中可以使用this.props;
- 根本原因是constructor会覆盖父类的constructor,导致你父类构造函数没执行,所以手动执行下。
render方法会根据tagName构建一个真正的DOM节点,然后设置这个节点的属性,最后递归地把自己的子节点也构建起来。所以只需要:
你不知道的Virtual DOM(这篇实现DOM写的好)
【React深入】深入分析虚拟DOM的渲染原理和特性
Virtual Dom 的优势在哪里?
答题思路:
传统DOM的劣势 --> react diff --> vdom出现解决什么问题 --> vdom的优势
传统前端编程方式是命令式的,直接操作DOM,代码可读性差可维护性低;
react的声明式编程,抛弃了直接操作DOM,只关注数据变动,DOM操作由框架完成,提升了可读性可维护性;
最初react在更新的过程中会刷新整个页面,后来引入的diff过程,对比数据变动前后的DOM结构,但DOM结构diff起来太复杂,由此引出了vdom;
VDOM 和 Diff 算法的出现是为了解决由命令式编程转变为声明式编程、数据驱动后所带来的性能问题的。换句话说,直接操作 DOM 的性能并不会低于虚拟 DOM 和 Diff 算法,甚至还会优于。
这么说的原因是因为 Diff 算法的比较过程,比较是为了找出不同从而有的放矢地更新页面。但是比较也是要消耗性能的。而直接操作 DOM 就是有的放矢,我们知道该更新什么不该更新什么,所以不需要有比较的过程。所以直接操作 DOM 效率可能更高。
React 厉害的地方并不是说它比 DOM 快,而是说不管你数据怎么变化,我都可以以最小的代价来进行更新 DOM。 方法就是我在内存里面用新的数据刷新一个虚拟 DOM 树,然后新旧 VDOM 进行比较,找出差异,再更新到 DOM 树上。
虚拟DOM的作用:
虚拟DOM的缺点:
首次渲染大量 DOM 时,由于多了一层虚拟 DOM 的计算,会比 innerHTML 插入慢。
虚拟 DOM 需要在内存中维护一份 DOM 的副本(跟上面一条其实也差不多,上面一条是从速度上,这条是从空间上)。
如果虚拟 DOM 大量更改,这是合适的。但是单一频繁地更新的话,虚拟 DOM 将会花费更多的时间处理计算的工作。所以,如果你有一个 DOM 节点相对较少页面,用虚拟 DOM,它实际上有可能会更慢。但对于大多数单页面应用,这应该都会更快。
把这篇文章看完 - 从 React 历史的长河里聊虚拟 DOM 及其价值
Virtual Dom 的优势在哪里?
diff 算法
React 在比较新旧 2 棵虚拟 DOM 树的时候,会同时考虑两点:
React 选用了启发式的算法,将时间复杂度控制在 O(n) 的级别。这个算法基于以下 2 个假设:
另外,React 只会对同一层的节点作比较,不会跨层级比较,如图所示:
Diff 使用的是深度优先算法,当遇到下图这样的情况:
最高效的算法应该是直接将 A 子树移动到 D 节点,但这样就涉及到跨层级比较,时间复杂度会陡然上升。React 的做法比较简单,它会先删除整个 A 子树,然后再重新创建一遍。结合到实际的使用场景,改变一个组件的从属关系的情况也是很少的。
同样道理,当 D 节点改为 G 节点时,整棵 D 子树也会被删掉,E、F 节点会重新创建。
对于列表的 Diff,节点的 key 有助于节点的重用
如上图所示,当没有 key 的时候,如果中间插入一个新节点,Diff 过程中从第三个节点开始的节点都是删除旧节点,创建新节点。当有 key 的时候,除了第三个节点是新创建外,第四和第五个节点都是通过移动实现的。
深挖key diff
下面我们来走一遍流程:
参考 - Diff 算法详解(主要看带key diff)
react diff 策略(这篇更全)
diff 策略(版本2)
tree diff
基于策略一,React 对树的算法进行了简洁明了的优化,即对树进行分层比较,两棵树只会对同一层次的节点进行比较。
即同一个父节点下的所有子节点。当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。这样只需要对树进行一次遍历,便能完成整个 DOM 树的比较。
component diff
React 是基于组件构建应用的,对于组件间的比较所采取的策略也是简洁高效。
element diff(不带key)
element diff(带key)
带key diff的过程看这里
深度优先遍历
二叉树与JavaScript
react 事件机制
【React深入】React事件机制
调用 setState 之后发生了什么?
setState 同步异步问题
详细解读见 - 你真的理解setState吗?
例题
React setState 笔试题,下面的代码输出什么?
render 函数的返回值类型有哪些
16.x render函数新增的返回类型
render 函数
在 render 函数中可以写 if ... else 吗
React的8种条件渲染方法
React 中如何减少 render 次数
React优化:竭尽全力的减少render渲染
性能!!让你的 React 组件跑得再快一点
constructor
中的super(props)
的作用?传与不传有什么区别?调用super的原因:
如果要从另一个角度看的话:
假设在es5要实现继承,首先定义一个父类:
再定义他sup的子类,继承sup的属性和方法:
这时调用父类生成一个实例化对象:
这就是es5中实现继承的方法。
而在es6中实现继承:
为什么我们要写 super(props) ?
Dialog 组件设计
React造轮系列:对话框组件 - Dialog 思路
The text was updated successfully, but these errors were encountered: