【React】React源码梳理笔记(十一)

前言

  • 结合前面知识,本篇粗略实现下fiber。

虚拟dom处理

  • 先对babel转译的createElement进行编写,完成基础功能,再给react-dom。

createElement.js

function createElement(type, config, ...children) {
    delete config.__self;
    delete config.__source;
    return {
        type,//类型
        props: {
            ...config,//属性
            children: children.map(child => {
                return typeof child === 'object' ? child : {//是字符串特殊处理
                    type: ELEMENT_TEXT,
                    props: { text: child, children: [] }
                }
            })
        }
    }
}

制作根节点

  • 然后将虚拟dom传给reactdom,编写react-dom,制作根节点:
function render(element, container) {
    let rootFiber = {
        tag: TAG_ROOT,
        stateNode: container,
        props: { children: [element] }
    }
    scheduleRoot(rootFiber);
}
const ReactDOM = {
    render
}
export default ReactDOM;
  • 也就是虚拟dom是在rootFiber的props的children属性上。挂载点在stateNode上。

初次渲染——虚拟dom转换为fiber树

  • 虚拟dom的孩子是一个数组形式,所以需要转换为fiber结构,我们以上一篇例子为例:
let C1 = { type: 'div', key: 'C1'};
let C2 = { type: 'div', key: 'C2'};
let B1 = { type: 'div', key: 'B1', child:[C1,C2] };
let B2 = { type: 'div', key: 'B2'};
let A1 = { type: 'div', key: 'A1', child:[B1,B2] };
  • 传统是这样结构,转换成fiber结构需要用循环遍历:
function createFiber(root,child){
    let index=0
    let prevFiber
    if(child){
        while(index<child.length){
            let currentChild=child[index]
            let fiber={
                type:currentChild.type,
                child:currentChild.child,
                key:currentChild.key,
                return:root
            }
            if(index===0){
                root.child=fiber
            }else{
                prevFiber.sibling=fiber
            }
            prevFiber=fiber
            index++
        }
    }
}
function deep(root){
    createFiber(root,root.child)
    if(root.child){
        deep(root.child,root.child.child)//这里改成return就可以拆开
    }
    while(root){
        if(root.sibling){
            deep(root.sibling,root.sibling.child)
        }
        root=root.return
    }
}
deep(A1)
console.log(A1)
  • 这里的技巧就是传2个值,一个是父节点,一个是它的孩子,然后修改父节点,将孩子进行while循环连接。
  • 最后输出:
    在这里插入图片描述
  • 每个节点只有一个孩子。
  • 这样就完成了转化fiber结构。而这个转化过程,是在上一篇console的start处进行的,所以结合一下idleCallback,写成这样:
function createDom(currentFiber) {
    if (currentFiber.tag === TAG_TEXT) {
        return document.createTextNode(currentFiber.props.text);
    } else if (currentFiber.tag === TAG_HOST) {
        let stateNode = document.createElement(currentFiber.type);
        updateDOM(stateNode, {}, currentFiber.props);
        return stateNode;
    }
}
updateDOM(stateNode,oldProps,newProps){
    setProps(stateNode,oldProps,newProps)//更新属性
}
let nextUnitOfWork=null
let workInProgressRoot=null
export function scheduleRoot(rootfiber){
    workInProgressRoot=rootfiber//work是根
    nextUnitOfWork=rootfiber//相当于每个工作单元,workloop里面会不断变化
}
function performUnitOfWork(currentFiber){   //这个就是前面deep函数样例的逻辑
    beginWork(currentFiber)//转化为fiber
    if(currentFiber.child){
        return currentFiber.child
    }
    while(currentFiber){
        completeUnitOfWork(currentFiber)
        if(currentFiber.sibling){
            return currentFiber.sibling
        }
        currentFiber=currentFiber.return
    }
}
function completeUnitOfWork(){//收集副作用

}
function beginWork(currentFiber){//如果原生dom,需要创建真实dom元素。current会变
    if(currentFiber.tag===TAG_ROOT){//根不需要建真实dom
        updateHostRoot(currentFiber)
    }else if(currentFiber.tag===TAG_TEXT){
        updateHostText(currentFiber)
    }else if(currentFiber.tag===TAG_HOST){
        updateHost(currentFiber)
    }
}

function updateHost(currentFiber){
    if(!currentFiber.stateNode){
        currentFiber.stateNode=createDom(currentFiber)
    }
    const newChildren = currentFiber.props.children
    reconcilieChildren(currentFiber,newChildren)
}
function updateHostText(currentFiber){
    if(!currentFiber.stateNode){
        currentFiber.stateNode=createDom(currentFiber)
    }
}
function updateHostRoot(currentFiber){
    let newChildren = currentFiber.props.children//取出当前任务的虚拟dom
    reconcilieChildren(currentFiber,newChildren)//为孩子创建fiber
}
function reconcilieChildren(currentFiber,newChildren){
    let newChildIndex=0
    let prevSibiling
    while(newChildIndex<newChildren.length){//while遍历孩子构建子节点
        let newChild = newChildren[newChildIndex]
        let tag
        if(newChild.type===ELEMENT_TEXT){
            tag=TAG_TEXT//字符串单独处理
        }else if(typeof newChild.type==='string'){
            tag=TAG_HOST//原生节点
        }
        let newFiber={
            tag,
            type:newChild.type,
            props:newChild.props,
            stateNode:null,
            return:currentFiber,
            effectTag:PLACEMENT,//增加操作
            nextEffect:null,//链表指向
        }
        if(newFiber){
            if(newChildIndex==0){//第一个儿子
                currentFiber.child=newFiber
            }else{
                prevSibiling.sibling=newFiber
            }
            prevSibiling=newFiber
        }
        newChildIndex++
    }
}
function workLoop(deadline){
    let shouldYield =false
    while(nextUnitOfWork&&!shouldYield){
        nextUnitOfWork=performUnitOfWork(nextUnitOfWork)
        shouldYield=deadline.timeRemaining()<=0
    }
    if(nextUnitOfWork){//出while则让给浏览器下次调度
       requestIdleCallback(workLoop,{timeout:1000})
    }else{
        console.log('over')
    }
}
requestIdleCallback(workLoop,{timeout:1000})
  • 其中reconcilieChildren对应上面的createFiber函数,一个逻辑。performUnitOfWork对应上面deep函数。看不懂的看看上面简写的就懂了。

初次渲染——收集副作用

  • 上一篇写到,在while那里会收集副作用,做成副作用链表,这次来编写下:
function completeUnitOfWork(currentFiber){//收集副作用
    let returnFiber=currentFiber.return
    if(returnFiber){//父节点
        if(!returnFiber.firstEffect){//这2个if的作用是为了确保根节点能指向链表的头和尾
            returnFiber.firstEffect=currentFiber.firstEffect
        }
        if(currentFiber.lastEffect){//说明他孩子有副作用(没孩子的进不来)
            if(returnFiber.lastEffect){//如果父有指针,把当前有副作用的孩子挂到链表下一个
                returnFiber.lastEffect.nextEffect=currentFiber.firstEffect
            }
            returnFiber.lastEffect=currentFiber.lastEffect
        }
        const effectTag=currentFiber.effectTag//如果这个节点有更新
        if(effectTag){
            if(returnFiber.lastEffect){//这个节点不是第一个,那就用next连接当前
                returnFiber.lastEffect.nextEffect=currentFiber
            }else{
                returnFiber.firstEffect=currentFiber
            }
            returnFiber.lastEffect=currentFiber//父节点的尾指针,始终指向最后个有副作用的孩子
        }
    }
}
  • 这个可能得稍微揣摩下,总之就是最终生成了一个副作用链表,没副作用的就跳过不收集。
  • 这个链表很巧的就是都是孩子收集完收集父亲才算完成,这样可以先生成子节点,然后再挂到父节点上,最后挂根上。

初次渲染——提交阶段

  • 提交阶段则在上面代码的console.log over处执行:
function workLoop(deadline){
    let shouldYield =false
    while(nextUnitOfWork&&!shouldYield){
        nextUnitOfWork=performUnitOfWork(nextUnitOfWork)
        shouldYield=deadline.timeRemaining()<=0
    }
    if(nextUnitOfWork){//出while则让给浏览器下次调度
       requestIdleCallback(workLoop,{timeout:1000})
    }else{
        console.log('over')
        commitRoot()
    }
}
function commitRoot(){
    let currentFiber=workInProgressRoot.firstEffect
    while(currentFiber){
        commitWork(currentFiber)
        currentFiber=currentFiber.nextEffect //单链表,一个个effect做
    }
    workInProgressRoot=null
}
function commitWork(currentFiber){
    if(!currentFiber)return 
    let returnFiber=currentFiber.return
    let returnDom=returnFiber.stateNode//拿到真实dom
    if(currentFiber.effectTag===PLACEMENT){
        returnDom.appendChild(currentFiber.stateNode)
    }
    currentFiber.effectTag=null
}
  • 这个就是顺着链表添加就ok了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章