react源码分析(3)-Fiber节点,树的介绍以及渲染过程总览

目录

Fiber节点内容

Fiber树结构

渲染过程(精简版)

总结


2021SC@SDUSC

Fiber节点内容

fiber是react中的多任务调度的方法,可以优化性能,在这里给出react中fiber的源码,其中DIFF更新是可以中断的,避免其长期占据线程,从而有更好的体验。

export type Fiber = {|
  // These first fields are conceptually members of an Instance. This used to
  // be split into a separate type and intersected with the other Fiber fields
  // but until Flow fixes its intersection bugs, we've merged them into a
  // single type.

  // An Instance is shared between all versions of a component. We can easily
  // break this out into a separate object to avoid copying so much to the
  // alternate versions of the tree. We put this on a single object for now to
  // minimize the number of objects created during the initial render.

  // Tag identifying the type of fiber.
  tag: WorkTag,

  // Unique identifier of this child.
  key: null | string,

  // The value of element.type which is used to preserve the identity during
  // reconciliation of this child.
  elementType: any,

  // The resolved function/class/ associated with this fiber.
  type: any,

  // The local state associated with this fiber.
  stateNode: any,

  // Conceptual aliases
  // parent : Instance -> return The parent happens to be the same as the
  // return fiber since we've merged the fiber and instance.

  // Remaining fields belong to Fiber

  // The Fiber to return to after finishing processing this one.
  // This is effectively the parent, but there can be multiple parents (two)
  // so this is only the parent of the thing we're currently processing.
  // It is conceptually the same as the return address of a stack frame.
  return: Fiber | null,

  // Singly Linked List Tree Structure.
  child: Fiber | null,
  sibling: Fiber | null,
  index: number,

  // The ref last used to attach this node.
  // I'll avoid adding an owner field for prod and model that as functions.
  ref:
    | null
    | (((handle: mixed) => void) & {_stringRef: ?string, ...})
    | RefObject,

  // Input is the data coming into process this fiber. Arguments. Props.
  pendingProps: any, // This type will be more specific once we overload the tag.
  memoizedProps: any, // The props used to create the output.

  // A queue of state updates and callbacks.
  updateQueue: mixed,

  // The state used to create the output
  memoizedState: any,

  // Dependencies (contexts, events) for this fiber, if it has any
  dependencies: Dependencies | null,

  // Bitfield that describes properties about the fiber and its subtree. E.g.
  // the ConcurrentMode flag indicates whether the subtree should be async-by-
  // default. When a fiber is created, it inherits the mode of its
  // parent. Additional flags can be set at creation time, but after that the
  // value should remain unchanged throughout the fiber's lifetime, particularly
  // before its child fibers are created.
  mode: TypeOfMode,

  // Effect
  flags: Flags,
  subtreeFlags: Flags,
  deletions: Array<Fiber> | null,

  // Singly linked list fast path to the next fiber with side-effects.
  nextEffect: Fiber | null,

  // The first and last fiber with side-effect within this subtree. This allows
  // us to reuse a slice of the linked list when we reuse the work done within
  // this fiber.
  firstEffect: Fiber | null,
  lastEffect: Fiber | null,

  lanes: Lanes,
  childLanes: Lanes,

  // This is a pooled version of a Fiber. Every fiber that gets updated will
  // eventually have a pair. There are cases when we can clean up pairs to save
  // memory if we need to.
  alternate: Fiber | null,

  // Time spent rendering this Fiber and its descendants for the current update.
  // This tells us how well the tree makes use of sCU for memoization.
  // It is reset to 0 each time we render and only updated when we don't bailout.
  // This field is only set when the enableProfilerTimer flag is enabled.
  actualDuration?: number,

  // If the Fiber is currently active in the "render" phase,
  // This marks the time at which the work began.
  // This field is only set when the enableProfilerTimer flag is enabled.
  actualStartTime?: number,

  // Duration of the most recent render time for this Fiber.
  // This value is not updated when we bailout for memoization purposes.
  // This field is only set when the enableProfilerTimer flag is enabled.
  selfBaseDuration?: number,

  // Sum of base times for all descendants of this Fiber.
  // This value bubbles up during the "complete" phase.
  // This field is only set when the enableProfilerTimer flag is enabled.
  treeBaseDuration?: number,

  // Conceptual aliases
  // workInProgress : Fiber ->  alternate The alternate used for reuse happens
  // to be the same as work in progress.
  // __DEV__ only

  _debugSource?: Source | null,
  _debugOwner?: Fiber | null,
  _debugIsCurrentlyTiming?: boolean,
  _debugNeedsRemount?: boolean,

  // Used to verify that the order of hooks does not change between renders.
  _debugHookTypes?: Array<HookType> | null,
|};

其中lanes为Fiber的优先级,用于调度。tag为类型,type说明是否为函数组件或者类组件,而alternate属性会在DIFF算法中见到,用于表示可替换的旧节点,memoizedProps代表上一次渲染时的Props,memoizedState代表上一次渲染时的State。

Fiber树结构

这里着重说一下stateNode,child,return以及sibling,其中stateNode为DOM节点,而child,return,sibling的关系,我想可以通过下图所示

其中父节点会存储子节点中的长子,以child字段表示,子节点的兄弟节点存储在sibling字段中,return表示其父节点,通过该数据结构遍历DOM并且进行渲染。

渲染过程(精简版)

下面给出假如用JS写出一个类react框架的精简代码,我依旧参考了网上的文章,但做了改进,不仅实现了原生组件,也实现了函数组件与类组件的渲染(当然与源码并不完全相同,但比较简洁的介绍了总体的原理,并且简明扼要的体现了react渲染的流程以及各个函数的作用)。

function Component(props){
    this.props=props
}
Component.prototype.isReactComponent={}
export default Component

 这里体现了组件的原理,通过在prototype中赋值方便react组件在reconcile过程中被辨识,这正是react源码中的处理方式,而其他组件则会继承该组件从而利用其中的函数,比如生命周期函数以及状态的保存。关于prototype,所有js对象都会从一个prototype(原型对象)中继承属性以及方法。详见:

https://www.cnblogs.com/wulihong/p/8906231.html

以下展示能够在总体上体现react渲染流程的代码:

let wipRoot=null
function render(vNode,container){
    wipRoot={
        type:'div',
        props:{
            children:{...vNode}
        },
        stateNode:container
    }
    nextUnitWork=wipRoot
}
function updateHostComponent(workInProgress){
    const {type,props}=workInProgress
   if(!workInProgress.stateNode){
       workInProgress.stateNode=createNode(workInProgress)
   }
   
   reconcilChildren(workInProgress,props.children)
}
function reconcilChildren(workInProgress,children){
    if(typeof children==='string'||typeof children==='number'){
        return;
    }
    const newChildren=Array.isArray(children)?children:[children]
    let previousFiber=null;
    for(let i=0;i<newChildren.length;i++){
        let child=newChildren[i]
        let newFiber={
            type:child.type,
            props:{...child.props},
            stateNode:null,
            child:null,
            sibling:null,
            return:workInProgress
        }
        if(typeof child==='string'){
            newFiber.props=child
        }
        if(i===0){
            workInProgress.child=newFiber

        }else{
            previousFiber.sibling=newFiber
        }
        previousFiber=newFiber
    }

}
function updateTextComponent(workInProgress){
    if(!workInProgress.stateNode){
        workInProgress.stateNode=document.createTextNode(workInProgress.props)
    }
}
function updateNode(node,nextVal){
    Object.keys(nextVal).forEach(k=>{
        if(k==='children'){
            if(typeof nextVal[k]==='string'){
                node.textContent=nextVal[k]
            }
        }else{
            node[k]=nextVal[k]
        }
    })
}
function updateFunctionComponent(workInProgress){
    // console.log(workInProgress)
    const {type,props}=workInProgress
    const resNode=type(props)
    // if(!workInProgress.stateNode){
    //     workInProgress.stateNode=createNode(resNode)
    // }
    reconcilChildren(workInProgress,resNode)
}
function updateClassComponent(workInProgress){
    const {type,props}=workInProgress
    const instace=new type(props)
    const resNode=instace.render(props)
    // if(!workInProgress.stateNode){
    //     workInProgress.stateNode=createNode(resNode)
    // }
    reconcilChildren(workInProgress,resNode)
}
function createNode(workInProgress){
    let node;
    const {type,props}=workInProgress
    node=document.createElement(type)
    updateNode(node,props)
    return node;
}
let nextUnitWork=null
function performUnitOfWork(workInProgress){
    const {type}=workInProgress
    if(typeof type === 'string'){
        updateHostComponent(workInProgress)
    }
    if(typeof type==='function'){
        if(type.prototype.isReactComponent){
            updateClassComponent(workInProgress)
        }else{
            updateFunctionComponent(workInProgress)
        }
    }
    if(typeof type==='undefined'){
        updateTextComponent(workInProgress)
    }
    if(workInProgress.child){
        return workInProgress.child
    }
    let nextFiber=workInProgress
    while(nextFiber){
        if(nextFiber.sibling){
            return nextFiber.sibling
        }
        nextFiber=nextFiber.return
    }
}
function workLoop(IdleDeadline){
    while(nextUnitWork&&IdleDeadline.timeRemaining()>1){
        nextUnitWork=performUnitOfWork(nextUnitWork)
    }
    if(!nextUnitWork&&wipRoot){
        commitRoot()
    }
}
function commitRoot(){
   commitWorker(wipRoot.child)
   wipRoot=null
}
function commitWorker(workInProgress){
    if(!workInProgress){
        return;
    }
    let parentFiber=workInProgress.return
    while(!parentFiber.stateNode){
        parentFiber=parentFiber.return
    }
    let parentNode=parentFiber.stateNode
    if(workInProgress.stateNode){
        parentNode.appendChild(workInProgress.stateNode)
    }
        commitWorker(workInProgress.child)
        commitWorker(workInProgress.sibling)
}
requestIdleCallback(workLoop)
export default {render}

 这里给出解释,其中requestIdleCallback为windows函数,意义为空闲时间时进行调用,从而进行合理调度(而react使用了自行编写的函数以优化,但原理相同),workLoop中会检测空闲时间与剩余任务,如果任务存在且空闲时间充足就会执行剩余任务(performUnitOfWork),当剩余任务结束时提交节点(commitRoot)。执行任务时会根据组件类型分别执行,并且返回当前节点的子节点,如果子节点不存在,那就寻找兄弟节点,如果兄弟节点也不存在,就继续层层向上遍历,直到找到下一个要执行的任务为止。在分组件类别执行任务的过程中(updateHostComponent,updateFunctionComponent,updateClassComponent),不仅要得到stateNode,还要协调子节点(reconcilChildren),遍历子节点,得到其child以及子节点的sibling,执行完毕后,退到 performUnitOfWork层 寻找下一节点。当全部执行完毕后,执行commitRoot时采用了递归的方法,将当前节点的stateNode挂载父节点的stateNode上,并且当前节点的child和sibling也要执行commitWorker。而render函数,只要规定好根节点Fiber并且将其赋给全局变量nextUnitWork即可。这就是极简的Fiber渲染的流程。

总结

介绍了Fiber节点以及相关的树结构,并且给出了具体渲染流程的简单介绍。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值