React是Facebook前端团队开源的一个将前端页面可组件化的一个库,它不是一个MVC框架,它鼓励开发人员去创建可复用的组件来构成页面。
便捷的DOM操作使得jQuery统治了Web开发近十年,在使用jQuery进行开发的时候,渲染和交互都是通过对DOM进行直接操作,但是DOM操作的开销成本是很昂贵的,频繁的DOM操作成为页面性能优化的痛点。而React的出现彻底颠覆了直接操作DOM的开发思维,它将视图与数据进行单向绑定,绝大部分操作都可以不再直接操作DOM,而是通过改变数据来更新视图,这对于前端是具有里程碑意义的。
React的框架实现可以分为两个部分,一部分叫做reconciliation(调和),另一部分为Rendering(渲染)。通过这两部分,React用当下越来越廉价的计算成本去替代多余并且昂贵的DOM操作来提升整个web app的性能。
我们知道每个React component都有自己的render方法,组件通过每次render方法返回的值(Virtual DOM)去通知React将要渲染的结果,React则会根据将前后两次render返回的值进行比较(diff),找出其中的不同,如果比较的结果有不同点,会将这个结果交给Rendering部分,并且只对比较的不同点进行精准update来达到减少多余DOM操作的目的。
那么新的问题来了,reconciliation是如何找出前后两次render的差别的呢?无非就是遍历两颗Virtual DOM树的节点进行比较了。
There are some generic solutions to this algorithmic problem of generating the minimum number of operations to transform one tree into another. However, the state of the art algorithms have a complexity in the order of O(n^3) where n is the number of elements in the tree.
然而即使当下硬件性价比已经越来越高,计算的成本已经越来越低,对两颗树进行遍历比较仍然是不可取的(传统 diff 算法的复杂度达到 O(n^3))。
If we used this in React, displaying 1000 elements would require in the order of one billion comparisons. This is far too expensive.
针对这个问题,React Diff通过下面几个方法,将diff算法的复杂度从O(n^3)降到了O(n):
Reconciliation结束了之后,将比较的结果交给Rendering部分,从而转化成相对应的DOM操作,更新页面。
正是因为React将实现分为了reconciliation和rendering两部分,所以reconciliation不仅仅适用于Web,同样适用于App(React Native),只需要实现不同环境所对应的Update部分即可。这也是为什么”Virtual DOM”这个词用的不太恰当的原因。
Fiber算法是Facebook React团队耗费两年时间打造的巅峰之作,它是对整个reconciliation部分进行的重写,Fiber能够使得动画、布局和页面交互变得更加的流畅。
我们知道当js在处理大型计算的时候会导致页面出现卡帧的现象,更严重的会出现页面“假死”。所以在这些情况下,必然会导致动画丢帧、不连贯,用户体验就特别差。为了解决这个问题,我们可以将大型的计算拆分成一个个小型计算,然后按照执行顺序异步调用,这样就不会长时间霸占线程,UI也能在两次小型计算的执行间隙进行更新,从而给予用户及时的反馈。Fiber就是这样做的,并且以一种更高逼格的方式实现了。
如果说v16.0之前的React解决了HOW(如何用最少的DOM操作成本来update视图)的问题,那么这一次Fiber的出现,在这个基础上还解决了WHEN(何时update视图的哪一部分)的问题。
- In a UI, it’s not necessary for every update to be applied immediately; in fact, doing so can be wasteful, causing frames to drop and degrading the user experience.
- Different types of updates have different priorities — an animation update needs to complete more quickly than, say, an update from a data store.
- A push-based approach requires the app (you, the programmer) to decide how to schedule work. A pull-based approach allows the framework (React) to be smart and make those decisions for you.
基于上述这些原因,Fiber实现了一个虚拟调用栈,并给所有的update进行优先级排序,如下:
'use strict';
export type PriorityLevel = 0 | 1 | 2 | 3 | 4 | 5;
module.exports = {
NoWork: 0, // No work is pending.
SynchronousPriority: 1, // For controlled text inputs. Synchronous side-effects.
AnimationPriority: 2, // Needs to complete before the next frame.
HighPriority: 3, // Interaction that needs to complete pretty soon to feel responsive.
LowPriority: 4, // Data fetching, or result from updating stores.
OffscreenPriority: 5, // Won't be visible but do the work in case it becomes visible.
};
然后根据这些update的优先级,来决定执行的顺序。我们可以看到动画和页面交互都是优先级比较高的,这也是Fiber能够使得动画、布局和页面交互变得更加的流畅的原因之一。
我尽量避开了晦涩难懂的概念名词,用最通俗的表达来解释核心理念,如果需要了解更多细节部分,可以戳官方文档