转载:React Fiber架构(浅显易懂)

性能优化是一个系统性的工程,若是只看到局部,引入算法,固然是越快越好; 但从总体来看,在关键点引入缓存,能够秒杀N多算法,或另辟蹊径,探索事件的本质,可能用户要的并非快……


React16启用了全新的架构,叫作Fiber,其最大的使命是解决大型React项目的性能问题,再顺手解决以前的一些痛点。css


痛点

主要有以下几个:前端

  • 组件不能返回数组,最见的场合是UL元素下只能使用LI,TR元素下只能使用TD或TH,这时这里有一个组件循环生成LI或TD列表时,咱们并不想再放一个DIV,这会破坏HTML的语义。
  • 弹窗问题,以前一直使用不稳定的unstable_renderSubtreeIntoContainer。弹窗是依赖原来DOM树的上下文,所以这个API第一个参数是组件实例,经过它获得对应虚拟DOM,而后一级级往上找,获得上下文。它的其余参数也很好用,但这个方法一直没有转正。。。
  • 异常处理,咱们想知道哪一个组件出错,虽然有了React DevTool,可是太深的组件树查找起来仍是很吃力。但愿有个方法告诉我出错位置,而且出错时能让我有机会进行一些修复工做
  • HOC的流行带来两个问题,毕竟是社区兴起的方案,没有考虑到ref与context的向下传递。
  • 组件的性能优化全凭人肉,而且主要集中在SCU,但愿框架能干些事情,即便不用SCU,性能也能上去。


解决进度

  • 16.0 让组件支持返回任何数组类型,从而解决数组问题; 推出createPortal API ,解决弹窗问题; 推出componentDidCatch新钩子, 划分出错误组件与边界组件, 每一个边界组件能修复下方组件错误一次, 再次出错,转交更上层的边界组件来处理,解决异常处理问题。
  • 16.2 推出Fragment组件,能够看做是数组的一种语法糖。
  • 16.3 推出createRef与forwardRef解决Ref在HOC中的传递问题,推出new Context API,解决HOC的context传递问题(主要是SCU做崇)
  • 而性能问题,从16.0开始一直由一些内部机制来保证,涉及到批量更新及基于时间分片的限量更新。


一个小实验


咱们能够经过如下实验来窥探React16的优化思想。java


function randomHexColor(){
    return "#" + ("0000"+ (Math.random() * 0x1000000 << 0).toString(16)).substr(-6);
}
setTimeout(function() {
    var k = 0;
    var root = document.getElementById("root");
    for(var i = 0; i < 10000; i++){
        k += new Date - 0 ;
        var el = document.createElement("div");
        el.innerHTML = k;
        root.appendChild(el);
        el.style.cssText = `background:${randomHexColor()};height:40px`;
    }
}, 1000);
复制代码



这是一个拥有10000个节点的插入操做,包含了innerHTML与样式设置,花掉1000ms。node


咱们再改进一下,分派次插入节点,每次只操做100个节点,共100次,发现性能异常的好!react


function randomHexColor() {
    return "#" + ("0000" + (Math.random() * 0x1000000 << 0).toString(16)).substr(-6);
}
var root = document.getElementById("root");
setTimeout(function () {
    function loop(n) {
        var k = 0;
        console.log(n);
        for (var i = 0; i < 100; i++) {
            k += new Date - 0;
            var el = document.createElement("div");
            el.innerHTML = k;
            root.appendChild(el);
            el.style.cssText = `background:${randomHexColor()};height:40px`;
        }
        if (n) {
            setTimeout(function () {
                loop(n - 1);
            }, 40);
        }
    }
    loop(100);
}, 1000);
复制代码



究其缘由是由于浏览器是单线程,它将GUI描绘,时间器处理,事件处理,JS执行,远程资源加载通通放在一块儿。当作某件事,只有将它作完才能作下一件事。若是有足够的时间,浏览器是会对咱们的代码进行编译优化(JIT)及进行热代码优化,一些DOM操做,内部也会对reflow进行处理。reflow是一个性能黑洞,极可能让页面的大多数元素进行从新布局。git

浏览器的运做流程github


渲染 -> tasks -> 渲染 -> tasks -> 渲染 -> tasks -> ....


这些tasks中有些咱们可控,有些不可控,好比setTimeout何时执行很差说,它老是不许时; 资源加载时间不可控。但一些JS咱们能够控制,让它们分派执行,tasks的时长不宜过长,这样浏览器就有时间优化JS代码与修正reflow!下图是咱们理想中的渲染过程web


总结一句,就是让浏览器休息好,浏览器就能跑得更快算法


如何让代码断开重连


JSX是一个快乐出奇蛋,一会儿知足你两个愿望:组件化标签化。而且JSX成为组件化的标准化语言。redux


<div>
   <Foo>
      <Bar />
   </Foo>
</div>
复制代码


但标签化是自然套嵌的结构,意味着它会最终编译成递归执行的代码。所以React团队称React16以前的调度器为栈调度器,栈没有什么很差,栈显浅易懂,代码量少,但它的坏处不能随意break掉,continue掉。根据咱们上面的实验,break后咱们还要从新执行,咱们须要一种链表的结构。

链表是对异步友好的。链表在循环时不用每次都进入递归函数,从新生成什么执行上下文,变量对象,激活对象,性能固然比递归好。

所以Reat16设法将组件的递归更新,改为链表的依次执行。若是页面有多个虚拟DOM树,那么就将它们的根保存到一个数组中。


ReactDOM.render(<A />, node1)
ReactDOM.render(<B />, node2)
//node1与node2不存在包含关系,那么这页面就有两棵虚拟DOM树
复制代码



若是仔细阅读源码,React这个纯视图库其实也是三层架构。在React15有虚拟DOM层,它只负责描述结构与逻辑;内部组件层,它们负责组件的更新, ReactDOM.render、 setState、 forceUpdate都是与它们打交道,能让你屡次setState,只执行一次真实的渲染, 在适合的时机执行你的组件实例的生命周期钩子; 底层渲染层, 不一样的显示介质有不一样的渲染方法,好比说浏览器端,它使用元素节点,文本节点,在Native端,会调用oc, java的GUI, 在canvas中,有专门的API方法。。。

虚拟DOM是由JSX转译过来的,JSX的入口函数是React.createElement, 可操做空间不大, 第三大的底层API也很是稳定,所以咱们只能改变第二层。

React16将内部组件层改为Fiber这种数据结构,所以它的架构名也改叫Fiber架构。Fiber节点拥有return, child, sibling三个属性,分别对应父节点, 第一个孩子, 它右边的兄弟, 有了它们就足够将一棵树变成一个链表, 实现深度优化遍历。




如何决定每次更新的数量


在React15中,每次更新时,都是从根组件或setState后的组件开始,更新整个子树,咱们惟一能作的是,在某个节点中使用SCU断开某一部分的更新,或者是优化SCU的比较效率。

React16则是须要将虚拟DOM转换为Fiber节点,首先它规定一个时间段内,而后在这个时间段能转换多少个FiberNode,就更新多少个。

所以咱们须要将咱们的更新逻辑分红两个阶段,第一个阶段是将虚拟DOM转换成Fiber, Fiber转换成组件实例或真实DOM(不插入DOM树,插入DOM树会reflow)。Fiber转换成后二者明显会耗时,须要计算还剩下多少时间。而且转换实例须要调用一些钩子,如componentWillMount, 若是是重复利用已有的实例,这时就是调用componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate,这时也会耗时。

为了让读者能直观了解React Fiber的运做过程,咱们简单实现一下ReactDOM.render, 但不保证会跑起来。

首先是一些简单的方法:


var queue = []
ReacDOM.render = function (root, container) {
    queue.push(root)
    updateFiberAndView()
}
function getVdomFormQueue() {
    return queue.shift()
}
function Fiber(vnode){
    for(var i in vnode){
        this[i] = vnode[i]
    }
    this.uuid = Math.random()
}
//咱们简单的Fiber目前来看,只比vdom多了一个uuid属性
function toFiber(vnode){
    if(!vnode.uuid){
       return new Fiber(vnode)
    }
    return vnode
}
复制代码


updateFiberAndView要实现React的时间分片,咱们先用setTimeout模拟。咱们暂时不用理会updateView怎么实现,可能它就是updateComponentOrElement中将它们放到又一个列队,需再出来执行insertBefore, componentDidMount操做呢!


function updateFiberAndView() {
    var now = new Date - 0;
    var deadline = new Date + 100;
    updateView() //更新视图,这会耗时,所以须要check时间
    if (new Date < deadline) {
        var vdom = getVdomFormQueue()
        var fiber = vdom, firstFiber
        var hasVisited = {}
        do {//深度优先遍历
            var fiber = toFiber(fiber);//A处
            if(!firstFiber){
                fibstFiber = fiber
            }
            if (!hasVisited[fiber.uuid]) {
                hasVisited[fiber.uuid] = 1
                //根据fiber.type实例化组件或者建立真实DOM
                //这会耗时,所以须要check时间
                updateComponentOrElement(fiber);
                if (fiber.child) {
                    //向下转换
                    if (newDate - 0 > deadline) {
                        queue.push(fiber.child)//时间不够,放入栈
                        break
                    }
                    fiber = fiber.child;
                    continue  //让逻辑跑回A处,不断转换child, child.child, child.child.child
                }
            }
            //若是组件没有children,那么就向右找
            if (fiber.sibling) {
                fiber = fiber.sibling;
                continue //让逻辑跑回A处
            }
            // 向上找
            fiber = fiber.return
            if(fiber === fibstFiber || !fiber){
               break
            }
        } while (1)
    }
    if (queue.length) {
        setTimeout(updateFiberAndView, 40)
    }
}
复制代码



里面有一个do while循环,每一次都是当心翼翼进行计时,时间不够就未来不及处理的节点放进列队。


updateComponentOrElement无非是这样:

function updateComponentOrElement(fiber){
    var {type, stateNode, props} = fiber
    if(!stateNode){
        if(typeof type === "string"){
            fiber.stateNode = document.createElement(type)
        }else{
            var context = {}//暂时免去这个获取细节
            fiber.stateNode = new type(props, context)
        }
    }
    if(stateNode.render){
        //执行componentWillMount等钩子
        children = stateNode.render()
    }else{
        children = fiber.childen
    }
    var prev = null;
     //这里只是mount的实现,update时还须要一个oldChildren, 进行key匹配,重复利用已有节点
    for(var i = 0, n = children.length; i < n; i++){
        var child = children[i];
        child.return = fiber;
        if(!prev){
            fiber.child = child
        }else{
            prev.sibling = child
        }
        prev = child;
    }
}
复制代码


所以这样Fiber的return, child, sibling就有了,能够happy地进行深度优先遍历了。

如何调度时间才能保证流畅


刚才的updateFiberAndView其实有一个问题,咱们安排了100ms来更新视图与虚拟DOM,而后再安排40ms来给浏览器来作其余事。若是咱们的虚拟DOM树很小,其实不须要100ms; 若是咱们的代码以后, 浏览器有更多其余事要干, 40ms可能不够。IE10出现了setImmediate,requestAnimationFrame这些新定时器,让咱们这些前端,其实浏览器有能力让页面更流畅地运行起来。

浏览器自己也不断进化中,随着页面由简单的展现转向WebAPP,它须要一些新能力来承载更多节点的展现与更新。

下面是一些自救措施:

  • requestAnimationFrame
  • requestIdleCallback
  • web worker
  • IntersectionObserver


咱们依次称为浏览器层面的帧数控制调用,闲时调用,多线程调用, 进入可视区调用。

requestAnimationFrame在作动画时常常用到,jQuery新版本都使用它。web worker在angular2开始就释出一些包,实验性地用它进行diff数据。IntersectionObserver能够用到ListView中。而requestIdleCallback是一个生脸孔,而React官方偏偏看上它。

刚才说updateFiberAndView有出两个时间段,一个给本身的,一个给浏览器的。requestAnimationFrame能帮咱们解决第二个时间段,从而确保总体都是60帧或75帧(这个帧数能够在操做系统的显示器刷新频率中设置)流畅运行。

咱们看requestIdleCallback是怎么解决这问题的



它的第一个参数是一个回调,回调有一个参数对象,对象有一个timeRemaining方法,就至关于new Date - deadline,而且它是一个高精度数据, 比毫秒更准确, 至少浏览器到底安排了多少时间给更新DOM与虚拟DOM,咱们不用管。第二个时间段也不用管,不过浏览器可能1,2秒才执行这个回调,所以为了保险起见,咱们能够设置第二个参数,让它在回调结束后300ms才执行。要相信浏览器,由于都是大牛们写的,时间的调度比你安排更有效率。

因而咱们的updateFiberAndView能够改为这样:


function updateFiberAndView(dl) {
    updateView() //更新视图,这会耗时,所以须要check时间
    if (dl.timeRemaining() > 1) {
        var vdom = getVdomFormQueue()
        var fiber = vdom, firstFiber
        var hasVisited = {}
        do {//深度优先遍历
            var fiber = toFiber(fiber);//A处
            if(!firstFiber){
                fibstFiber = fiber
            }
            if (!hasVisited[fiber.uuid]) {
                hasVisited[fiber.uuid] = 1
                //根据fiber.type实例化组件或者建立真实DOM
                //这会耗时,所以须要check时间
                updateComponentOrElement(fiber);
                if (fiber.child) {
                    //向下转换
                     if (dl.timeRemaining() > 1) {
                        queue.push(fiber.child)//时间不够,放入栈
                        break
                    }
                    fiber = fiber.child;
                    continue  //让逻辑跑回A处,不断转换child, child.child, child.child.child
                }
            }
            //....略
        } while (1)
    }
    if (queue.length) {
        requetIdleCallback(updateFiberAndView, {
           timeout:new Date + 100
        }
      )
    }
}
复制代码


到这里,ReactFiber基于时间分片的限量更新讲完了。实际上React为了照顾绝大多数的浏览器,本身实现了requestIdleCallback。

批量更新


但React团队以为还不够,须要更强大的东西。由于有的业务对视图的实时同步需求并不强烈,但愿将全部逻辑都跑完才更新视图,因而有了batchedUpdates,目前它还不是一个稳定的API,所以你们使用它时要这样用ReactDOM.unstable_batchedUpdates。

这个东西怎么实现呢?就是搞一个全局的开关,若是打开了,就让updateView不起做用。


var isBatching = false
function batchedUpdates(callback, event) {
    let keepbook = isBatching;
    isBatching = true;
    try {
        return callback(event);
    } finally {
        isBatching = keepbook;
        if (!isBatching) {
            requetIdleCallback(updateFiberAndView, {
               timeout:new Date + 1
            }
        }
    }
};

function updateView(){
   if(isBatching){
      return
   }
   //更新视图
}
复制代码



事实上,固然没有这么简单,考虑到你们看不懂React的源码,你们能够看一下anujs是怎么实现的:

github.com/RubyLouvre/…


React内部也大量使用batchedUpdates来优化用户代码,好比说在事件回调中setState,在commit阶段的钩子(componentDidXXX)中setState 。

能够说,setState是对单个组件的合并渲染,batchedUpdates是对多个组件的合并渲染。合并渲染是React最主要的优化手段。


为何使用深度优化遍历


React经过Fiber将树的遍历变成了链表的遍历,但遍历手段有这么多种,为何恰恰使用DFS?!

这涉及一个很经典的消息通讯问题。若是是父子通讯,咱们能够经过props进行通讯,子组件能够保存父的引用,能够随时call父组件。若是是多级组件间的通讯,或不存在包含关系的组件通讯就麻烦了,因而React发明了上下文对象(context)。

context一开始是一个空对象,为了方便起见,咱们称之为unmaskedContext

当它遇到一个有getChildContext方法的组件时,那个方法会产生一个新context,与上面的合并,而后将新context做为unmaskedContext往下传。

当它遇到一个有contextTypes的组件,context就抽取一部份内容给这个组件进行实例化。这个只有部份内容的context,咱们称之为maskedContext

组件老是从unmaskedContext中割一块肉下来做为本身的context。可怜!

若是子组件没有contextTypes,那么它就没有任何属性。

在React15中,为了传递unmaskedContext,因而大部分方法与钩子都留了一个参数给它。但这么大架子的context居然在文档中没有什么地位。那时React团队尚未想好如何处理组件通讯,所以社区一直用舶来品Redux来救命。这状况一直到Redux的做者入主React团队。

还有一个隐患,它可能被SCU比较时是用maskedContext,而不是unmaskedContext。

基于这些问题,终于new Context API出来了。首先, unmaskedContext 再也不像之前那样各个方法中来往穿梭了,有一个独立的contextStack。开始时就push进一个空对象,到达某个组件须要实例化时,就取它第一个。当再次访问这个组件时, 就像它从栈中弹出。所以咱们须要深度优先遍历,保证每点节点都访问两次。



相同的状况还有container,container是咱们某个元素虚拟DOM须要用到的真实父节点。在React15中,它会装在一个containerInfo对象也层层传送。

咱们知道,虚拟DOM分红两大类,一种是组件虚拟DOM,type为函数或类,它自己不产生节点,而是生成组件实例,而经过render方法,产生下一级的虚拟DOM。一种是元素虚拟DOM,type为标签名,会产生DOM节点。上面的元素虚拟DOM的stateNode(DOM节点),就是下方的元素虚拟DOM的contaner。

这种独立的栈机制有效地解决了内部方法的参数冗余问题。

但有一个问题,当第一次渲染完毕后,contextStack置为空了。而后咱们位于虚拟DOM树的某个组件setState,这时它的context应该如何获取呢?React的解决方式是,每次都是从根开始渲染,经过updateQueue加速跳过没有更新的 节点——每一个组件在setState或forceUpdate时,都会建立一个updateQueue属性在它的上面。anujs则是保存它以前的unmaskedContext到实例上,unmaskedContext能够看做是上面全部context的并集,而且一个能够当多个使用。

当咱们批量更新时,可能有多少不连续的子组件被更新了,其中两个组件之间的某个组件使用了SCU return false,这个SCU应该要被忽视。 所以咱们引用一些变量让它透明化。就像forceUpdate能让组件无视SCU同样。


为何要对生命周期钩子大换血


React将虚拟DOM的更新过程划分两个阶段,reconciler阶段与commit阶段。reconciler阶段对应早期版本的diff过程,commit阶段对应早期版本的patch过程。

一些迷你React,如preact会将它们混合在一块儿,一边diff一边patch(幸亏它使用了Promise.then来优化,确保每次只更新一个组件) 。

有些迷你React则是经过减小移动进行优化,因而绞尽脑汁,用上各类算法,最短编辑距离,最长公共子序列,最长上升子序列。。。

其实基于算法的优化是一种绝望的优化,就相似玛雅文明由于找不到铜矿一直停留于石器时代,诞生了伟大的工匠精神把石器打磨得美伦美奂。



之因此这么说,由于diff算法都用于组件的新旧children比较,children通常不会出现过长的状况,有点大炮打蚊子。何况当咱们的应用变得很是庞大,页面有上万个组件,要diff这么多组件,再卓绝的算法也不能保证浏览器不会累趴。由于他们没想到浏览器也会累趴,也没有想到这是一个长跑的问题。若是是100米短跑,或者1000米竞赛,固然越快越好。若是是马拉松,就须要考虑到保存体力了,须要注意休息了。性能是一个系统性的工程。

在咱们的代码里面,休息就是检测时间而后断开Fiber链。

updateFiberAndView里面先进行updateView,因为节点的更新是不可控,所以所有更新完,才检测时间。而且咱们彻底不用担忧updateView会出问题,由于updateView实质上是在batchedUpdates中,里面有try catch。而接下来咱们基于DFS更新节点,每一个节点都要check时间,这个过程其实很惧怕出错的, 由于组件在挂载过程当中会调三次钩子/方法(constructor, componentWillMount, render), 组件在更新过程当中会调4次钩子 (componentWillReceiveProps, shouldUpdate, componentWillUpdate), 总不能每一个方法都用try catch包起来,这样会性能不好。而constructor, render是不可避免的,因而对三个willXXX动刀了。

在早期版本中,componentWillMount与componentWillReceiveProps会作内部优化,执行屡次setState都会延后到render时进行合并处理。所以用户就肆意setState了。这些willXXX还可让用户任意操做DOM。 操做DOM会可能reflow,这是官方不肯意看到的。因而官方推出了getDerivedStateFromProps,让你在render设置新state,你主要返回一个新对象,它就主动帮你setState。因为这是一个静态方法,你不能操做instance,这就阻止了你屡次操做setState。因为没有instance,也就没有instance.refs.xxx,你也没有机会操做DOM了。这样一来,getDerivedStateFromProps的逻辑应该会很简单,这样就不会出错,不会出错,就不会打断DFS过程。

getDerivedStateFromProps取代了原来的componentWillMount与componentWillReceiveProps方法,而componentWillUpdate原本就是无关紧要,之前彻底是为了对称好看。

在即便到来的异步更新中,reconciler阶段可能执行屡次,才执行一次commit,这样也会致使willXXX钩子执行屡次,违反它们的语义,它们的废弃是不可逆转的。

在进入commi阶段时,组件多了一个新钩子叫getSnapshotBeforeUpdate,它与commit阶段的钩子同样只执行一次。

若是出错呢,在componentDidMount/Update后,咱们可使用componentDidCatch方法。因而整个流程变成这样:



reconciler阶段的钩子都不该该操做DOM,最好也不要setState,咱们称之为轻量钩子*。commit阶段的钩子则对应称之为重量钩子**。


任务系统


updateFiberAndView是位于一个requestIdleCallback中,所以它的时间颇有限,分给DFS部分的时间也更少,所以它们不能作太多事情。这怎么办呢,标记一下,留给commit阶段作。因而产生了一个任务系统。

每一个Fiber分配到新的任务时,就经过位操做,累加一个sideEffect。sideEffect字面上是反作用的意思,很是重FP流的味道,但咱们理解为任务更方便咱们的理解。

每一个Fiber可能有多个任务,好比它要插入DOM或移动,就须要加上Replacement,须要设置样式,须要加上Update。

怎么添加任务呢?

fiber.effectTag |= Update
复制代码


怎么保证不会重复添加相同的任务?

fiber.effectTag &= ~DidCapture;
复制代码


在commit阶段,怎么知道它包含了某项任务?

if(fiber.effectTag & Update){ /*操做属性*/}
复制代码



React内置这么多任务,从DOM操做到Ref处理到回调唤起。。。



顺便说一下anu的任务名,是基于素数进行乘除。

github.com/RubyLouvre/…


不管是位操做仍是素数,咱们只要保证某个Fiber的相同性质任务只执行一次就好了。

此外,任务系统还有另外一个存在乎义,保证一些任务优先执行,某些任务是在另外一些任务以前。咱们称之为任务分拣。这就像快递的仓库管理同样,有了归类才好进行优化。好比说,元素虚拟DOM的插入移动操做必须在全部任务以前执行,移除操做必须在componentWillUnmount后执行。这些任务之因此是这个顺序,由于这样作才合理,都通过高手们的严密推敲,通过React15时代的大众验证。


Fiber的连体婴结构


连体婴是一个可怕的名词,想一想就不舒服,由于事实上Fiber就是一个不寻常的结构,直到如今个人anujs尚未很好实现这结构。Fiber有一个叫alternate的属性,大家称之为备胎,替死鬼,替身演员。你也能够视它为git的开发分支,稳定没错的那个则是master。每次 setState时,组件实例stateNode上有一个_reactInternalFiber的对象,就是master分支,而后当即复制一个如出一辙的专门用来踩雷的alternate对象。

alternate对象会接受上方传递下来的新props,而后从getDerivedStateFromProps获得新state,因而render不同的子组件,子组件再render,渐渐的,master与alternate的差别愈来愈大,当某一个子组件出错,因而咱们又回滚到该边界组件的master分支。

能够说,React16经过Fiber这种数据结构模拟了git的三种重要操做, git add, git commit, git revert。

有关连体婴结构的思考,能够参看我另外一篇文章《从错误边界到回滚到MWI》,这里就再也不展开。


中间件系统


提及中间件系统,你们可能对koa与redux里面的洋葱模型比较熟悉。



早在React15时代,已经有一个叫Transaction的东西,与洋葱模型如出一辙。在 Transaction 的源码中有一幅特别的 ASCII 图,形象的解释了 Transaction 的做用。



简单地说,一个Transaction 就是将须要执行的 method 使用 wrapper 封装起来,再经过 Transaction 提供的 perform 方法执行。而在 perform 以前,先执行全部 wrapper 中的 initialize 方法;perform 完成以后(即 method 执行后)再执行全部的 close 方法。一组 initialize 及 close 方法称为一个 wrapper,从上面的示例图中能够看出 Transaction 支持多个 wrapper 叠加。

这个东西有什么用呢? 最少有两个用处,在更新DOM时,收集当前获取焦点的元素与选区,更新结束后,还原焦点与选区(由于插入新节点会引发焦点丢失,document.activeElement变成body,或者是autoFocus,让焦点变成其余input,致使咱们正在输入的input的光标不见了,没法正常输入)。在更新时,咱们须要保存一些非受控组件,在更新后,对非受控组件进行还原(非受控组件是一个隐涩的知识点,目的是让那些没有设置onChange的表单元素没法手动改变它的值)。固然了,contextStack, containerStack的初次入栈与清空也能够作成中间件。中间件就是分布在batchedUpdates的两侧,一种很是易于扩展的设计,为何很少用用呢!


总结


React Fiber是对React来讲是一次革命,解决了React项目严重依赖于手工优化的痛点,经过系统级别的时间调度,实现划时代的性能优化。鬼才般的Fiber结构,为异常边界提供了退路,也为限量更新提供了下一个起点。React团队的人才辈出,创造力非凡,别出心裁,从更高的层次处理问题,这是其余开源团队不可多见。这也是我一直选择与学习React的缘由所在。

相关文章
相关标签/搜索