原生节点的首次渲染

295 阅读2分钟

⚠️:有些api和方法的逻辑后面会改,现在只是临时顶替一下

window.requestIdleCallback

简单的说,判断一帧有空闲时间,则去执行某个任务。目的是为了解决当任务需要长时间占用主进程,导致更高优先级任务(如动画或事件任务),无法及时响应,而带来的页面丢帧(卡死)情况。故RequestIdleCallback 定位处理的是: 不重要且不紧急的任务。

window.requestIdleCallback(callback[, options])

返回值 一个ID,可以把它传入 Window.cancelIdleCallback() 方法来结束回调。

callback 一个在事件循环空闲时即将被调用的函数的引用。函数会接收到一个名为 IdleDeadline 的参数,这个参数可以获取当前空闲时间以及回调是否在超时时间前已经执行的状态。

options 可选 包括可选的配置参数。具有如下属性:timeout:如果指定了timeout,并且有一个正值,而回调在timeout毫秒过后还没有被调用,那么回调任务将放入事件循环中排队,即使这样做有可能对性能产生负面影响。

进过createroot后自身的fiber生成了,得开始调用performUnitOfWork进行节点更新

1.在workloop.js中写下

window.requestIdleCallback(workloop)
function workloop(IdleDeadline){
  /**
   * @timeRemaining 拿到当前浏览器的空闲时间
  */
  while(wip && IdleDeadline.timeRemaining() > 0){
    //判断是否有任务,有的话调用performUnitOfWork(),这样就可以从本身一直更新到子元素
    performUnitOfWork()
  }

  //经过performUnitOfWork的更新后wip为null,则说明wip更新完成,开始和wipRoot对比,
  if(!wip && wipRoot){
    commitRoot()
  }
}

performUnitOfWork就是进行节点更新,先放一边, 来看看commitRoot()

//提交
function commitRoot(){
  //处理render(jsx)里面的jsx更新
  commitWorker(wipRoot)
  //方便workloop下次调用不会影响根节点
  wipRoot = null
}

/**
 * @wip 需要更新的节点
*/

function commitWorker(wip){
  if(!wip){
    return
  }

  //1.提交自己
  const {flags,stateNode} = wip
  const parentNode = wip.return.stateNode
  if(flags & Placement && stateNode){
    //插入到父节点(parentNode)
    parentNode.appendChild(stateNode)
  }
  //2.提交子节点
  commitWorker(wip.child)
  //3.提交兄弟节点
  commitWorker(wip.sibling)
}

在调用commitWorker时,wip进过performUnitOfWork()的加工已经具备了,stateNode,child,sibling

这回来看看performUnitOfWork()

wip进入,根据tag判断节点类型,进行对应的操作

来到ReactFilberReconciler.js中

加工filber的stateNode

export function updatedHostComponent(wip) {
  //初次渲染表示没有该DOM节点
  if (!wip.stateNode) {
    wip.stateNode = document.createElement(wip.type);
    updateNode(wip.stateNode,wip.props)
  }
  //遍历子节点
  //wip.props.children;render的原始对象中的子节点集合,如果子节点为1个为对象,多个为数组
  //文本节点props的child为其内容
  //a标签则是其href和child文本内容
  reconcileChildren(wip,wip.props.children)
  console.log('wip',wip);
}

reconcileChildren:加工filber的child个和sibiling

function reconcileChildren(wip,children){
    //如果是字符串什么的
    if(isStringOrNumber(children)){
        return
    }
    //处理是节点的情况下,生成新数组
    const newChildren = isArray(children) ? children:[children]
    //  记录上一个节点
    let previousNewFiber = null;
    for(let i = 0;i<newChildren.length;i++){
        const newChild = newChildren[i]
        if(!newChild){
            continue;
        }
        //拿到一个子节点就更新一下当前子节点的
        //1.子节点本身的fiber
        const newFiber = createFiber(newChild,wip)
        //previousNewFiber为null则说明是fiber的第一个子节点
        //后续遇到null的节点可以一直代替第一个,直到拿到有效的节点
        if(!previousNewFiber){
            wip.child = newFiber
            //当前wip的子节点有了,在performUnitOfWork就可以一直循环进行操作
        }else{
            //上一个节点的兄弟节点就是当前节点本身
            previousNewFiber.sibling = newFiber
        }

        previousNewFiber = newFiber
    }
}

updateNode(wip.stateNode,wip.props),这个是个更新节点自身的的属性

/**
* @node 当前节点本身stateNode
* @nextval 当前节点的props
* 
*/
export function updateNode(node,nextval){
 Object.keys(nextval).forEach(key => {
   if(key === 'children'){
     //判断是不是文本节点
     if(isStringOrNumber(nextval[key])){
       node.textContent = nextval[key]+''
     }
   }else{
     node[key] = nextval[key]
   }
 })

}

至此,通过commitWorker中不断的appendchild逐层渲染了root下的子组件

WX20220617-015132@2x.png