美文网首页纵横研究院React技术专题社区
react源码阅读- fiber架构探索(二)

react源码阅读- fiber架构探索(二)

作者: konnga | 来源:发表于2019-06-07 12:56 被阅读11次

    react源码阅读- fiber架构探索(二)

    React 团队在 React 的v16版本中重写了 React 的核心算法 - reconciliation,称为fiber reconciler,简称为Fiber。

    Fiber Tree

    Fiber Reconciler 在阶段一进行 Diff 计算的时候,会生成一棵 Fiber 树。这棵树是在 Virtual DOM 树的基础上增加额外的信息来生成的,它本质来说是一个链表。

    image

    Fiber 树在首次渲染的时候会一次过生成。在后续需要 Diff 的时候,会根据已有树和最新 Virtual DOM 的信息,生成一棵新的树。这颗新树每生成一个新的节点,都会将控制权交回给主线程,去检查有没有优先级更高的任务需要执行。如果没有,则继续构建树的过程:

    image

    如果过程中有优先级更高的任务需要进行,则 Fiber Reconciler 会丢弃正在生成的树,在空闲的时候再重新执行一遍。

    在构造 Fiber 树的过程中,Fiber Reconciler 会将需要更新的节点信息保存在Effect List当中,在阶段二执行的时候,会批量更新相应的节点。

    Fiber reconciler

    reconcile过程分为2个阶段(phase):

    1.(可中断)render/reconciliation 通过构造workInProgress tree得出change

    2.(不可中断)commit 应用这些DOM change

    render/reconciliation

    以fiber tree为蓝本,把每个fiber作为一个工作单元,自顶向下逐节点构造workInProgress tree(构建中的新fiber tree);

    具体过程如下(以组件节点为例):

    1. 如果当前节点不需要更新,直接把子节点clone过来,跳到5;要更新的话打个tag

    2. 更新当前节点状态(props, state, context等)

    3. 调用shouldComponentUpdate(),false的话,跳到5

    4. 调用render()获得新的子节点,并为子节点创建fiber(创建过程会尽量复用现有fiber,子节点增删也发生在这里)

    5. 如果没有产生child fiber,该工作单元结束,把effect list归并到return,并把当前节点的sibling作为下一个工作单元;否则把child作为下一个工作单元

    6. 如果没有剩余可用时间了,等到下一次主线程空闲时才开始下一个工作单元;否则,立即开始做

    7. 如果没有下一个工作单元了(回到了workInProgress tree的根节点),第1阶段结束,进入pendingCommit状态

    实际上是1-6的工作循环,7是出口,工作循环每次只做一件事,做完看要不要喘口气。工作循环结束时,workInProgress tree的根节点身上的effect list就是收集到的所有side effect(因为每做完一个都向上归并)

    所以,构建workInProgress tree的过程就是diff的过程,通过requestIdleCallback来调度执行一组任务,每完成一个任务后回来看看有没有插队的(更紧急的),每完成一组任务,把时间控制权交还给主线程,直到下一次requestIdleCallback回调再继续构建workInProgress tree

    Fiber之前的reconciler被称为Stack reconciler,就是因为这些调度上下文信息是由系统栈来保存的。虽然之前一次性做完,强调栈没什么意义,起个名字只是为了便于区分Fiber reconciler

    requestIdleCallback

    通知主线程,在有空闲时执行回调。

    
    window.requestIdleCallback(callback[, options])
    
    // 示例
    
    let handle = window.requestIdleCallback((idleDeadline) => {
    
        const {didTimeout, timeRemaining} = idleDeadline;
    
        console.log(`超时了吗?${didTimeout}`);
    
        console.log(`可用时间剩余${timeRemaining.call(idleDeadline)}ms`);
    
        // do some stuff
    
        const now = +new Date, timespent = 10;
    
        while (+new Date < now + timespent);
    
        console.log(`花了${timespent}ms搞事情`);
    
        console.log(`可用时间剩余${timeRemaining.call(idleDeadline)}ms`);
    
    }, {timeout: 1000});
    
    // 输出结果
    
    // 超时了吗?false
    
    // 可用时间剩余49.535000000000004ms
    
    // 花了10ms搞事情
    
    // 可用时间剩余38.64ms
    
    

    需要注意的是,requestIdleCallback调度只是希望做到流畅体验,并不能绝对保证什么,例如:

    
    // do some stuff
    
    const now = +new Date, timespent = 300;
    
    while (+new Date < now + timespent);
    
    

    如果对应React中的生命周期函数等时间上不受React控制的东西就花了300ms,什么机制也保证不了流畅。

    commit

    第2阶段直接一口气做完:

    1. 处理effect list(包括3种处理:更新DOM树、调用组件生命周期函数以及更新ref等内部状态)

    2. 出对结束,第2阶段结束,所有更新都commit到DOM树上了

    同步执行,这个阶段的实际工作量是比较大的,所以尽量不要在后3个生命周期函数里干重活儿。

    生命周期hook

    生命周期函数也被分为2个阶段了:

    
    // 第1阶段 render/reconciliation
    
    componentWillMount
    
    componentWillReceiveProps
    
    shouldComponentUpdate
    
    componentWillUpdate
    
    // 第2阶段 commit
    
    componentDidMount
    
    componentDidUpdate
    
    componentWillUnmount
    
    

    第1阶段的生命周期函数可能会被多次调用,默认以low优先级(后面介绍的6种优先级之一)执行,被高优先级任务打断的话,稍后重新执行。

    参考文章:

    1. react

    2. reconciliation

    相关文章

      网友评论

        本文标题:react源码阅读- fiber架构探索(二)

        本文链接:https://www.haomeiwen.com/subject/lwvrxctx.html