目录
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节点以及相关的树结构,并且给出了具体渲染流程的简单介绍。