首次渲染

59 阅读4分钟

渲染

  • 渲染需要走render函数
  • 那么就需要找虚拟dom

babel编译

const obj = {
    __self:null,
    __source:null,
    ref:null,
    key:null
};
const REACT_ELEMENT = Symbol.for('react.typeof')
const ReactElement = (type,key,ref,props)=>{
    return {
        $$typeof: REACT_ELEMENT,
        type,
        key,
        ref,
        props
    }
}
const jsxDev = (type, config)=>{
    let ref=null,key=null,props={},propName;
    if(config.ref){
        ref=config.ref;
    }
    if(config.key){
        key=config.key;
    }
    for(propName in config){
        if(config.hasOwnProperty(propName)&&!obj.hasOwnProperty(propName)){
            props[propName]=config[propName]
        }
    };
    return ReactElement(type,key,ref,props);
}
  • 我们会通过babel讲jsx转换成object的形式,那么函数执行的时候jsx函数就会将object变为虚拟dom,本质还是object
let element = <div>1</div>

编译后
let element = jsxDev('div',{child:1})

render函数介绍

  • render会从实例中拿到刚才存储的FiberRoot
  • 然后使用element来生成一次update,并放入根fiber的更新队列中
  • 渲染时需要使用异步渲染操作,因为浏览器渲染与js执行互斥,如果单个任务占用时间过长会导致页面卡死
  • 这时候需要有一个方法,在浏览器空闲的时候执行我们的更新,执行完成后将控制权给到浏览器,后续依次循环执行
  • 屏幕刷新频率在60hz,那么1秒平分的话大约为16.6ms,所以留点时间判断
  • requestIdleCallback,示例如下
    • 因为是浏览器调用,所以可以把它理解成异步宏任务
const works = [1,2,3,4,5];
const sleep = (time)=>{
    for(let o = Date.now();Date.now-o>time;){}
}
const run = ()=>{
    sleep(works.shift());
}
const processWork = (deadline)=>{
    if(deadline.timeRemaining>5&&works.length>0){
        run()
    }else if(works.length>0){
        requestIdleCallback(processWork);
    }
};

requestIdleCallback(processWork);
//同异步测试
requestIdleCallback((deadline) => {
    console.log('异步宏执行');
});
console.log('同步');
Promise.resolve().then(() => {
    console.log('微任务');
})

// 同步、微任务、异步宏

渲染前需要根据vdom创建fiber树

  • 先创建一个新的fiber空树,复制旧树的child、stateNode等属性,并且两者通过alternate关联
  • 创建顺序:
    • 以while循环遍历公共变量
    • 先通过beginWork查找子节点,将子节点生成为fiber节点,然后继续向下寻找,找不到了就执行完成步骤
    • 在完成步骤中,先将fiber变为真实dom,且将fiber的stateNode指向真实dom,然后把子节点的所有副作用累加到自身,并将子节点添加到自身
    • 然后查找sibling,如果sibling不为null,将sibling给到beginWork,否则就继续拿到父级,依次向上走,最终找不到父为止,停止完成
let element = (<h1>
    <div>
        1
        <span>2</span>
        !
    </div>
</h1>)

// 执行顺序
/*
新fiber根开始
将element从更新队列中执行,并拿到,然后生成fiber结构,当做新fiber根的child
有child,继续执行beginWork,拿到h1的fiber中pendingProps的children,是div,给div变为fiber,并当做h1的child
有child,执行beginWork,找到children,是数组,将其依次生成fiber,并以sibling想关联,并将第一个fiber给到div的child
有child,执行beginWork,但1为文本,没儿子,执行完成阶段
创建文本标签,将文本标签给到fiber的stateNode,有sibling,退出完成,进入beginWork
span的有子节点,在pendingProps中,将2变为fiber,然后继续beginWork
2没子节点了,走完成阶段
创建文本标签,将文本标签给到fiber的stateNode,没有sibling,找父级
将span生成真实dom,并给到fiber的stateNode,然后将其子节点(2)追加到自身,然后计算fiber的副作用,找sibling,存在,走beginWork
!没有子节点,走完成阶段,生成文本节点,赋值给stateNode,然后没sibling,走父级
父级为h1,那么创建h1的真实dom,给到stateNode,然后将子集追加到自身,并统计子集的flags,没sibling,找父级
根fiber,它不是个react节点,那么只冒泡,将副作用累加,然后没sibling没父级,结束

这个时候workInProgress也为null了
*/

副作用

  • 副作用为增删改查的标记(不同的数值),在创建fiber或更新删除的时候会给fiber添加
  • 初次挂载的时候,只有根节点的child有一个添加副作用,其他都是全新的,没有标记
  • flags为自身的副作用
  • subtreeFlags为后代节点的副作用总和

提交阶段

  • fiber和dom已经在内存中创完成了
  • 这个时候需要将它添加到页面中去
  • 我们刚才创建了一个新的根fiber,并且在上面进行更新和组装的,对应在根上的索引的就是root.current.alternate,把它给到root.finishedWork
  • 然后开始处理,我们在dom的组装阶段累加了副作用,那么现在我们需要判断下,有副作用的就执行,如果没有副作用不需要处理
  • 顺序:先按照子的副作用去处理,然后处理自身
  • 在处理过程中,删除自身的副作用,然后插入到真实节点