学习资料来自于下面链接。视频代码有些地方感觉不清晰,另外text对象创建跟我理解有些偏差,所以改了些地方,新版react会自动解析jsx语法,这儿写页面demo可以直接用createElement方法创建虚拟dom。

06-fiber架构实现 - 妙味课堂

简单版

function createElement(type, props, ...children) {
    return {
        type,
        props: {
            ...props,
            children: children.map(child => {
                return typeof child === 'object' ? child : createTextElement(child);
            })
        }
    }
}

function createTextElement(text) {
    return text
}
function render(vdom, container) {
    if (typeof vdom === 'string') {
        container.innerText = vdom;
    } else {
        const dom = document.createElement(vdom.type);
        Object.keys(vdom.props)
            .filter(key => key !== 'children')
            .forEach(name => dom[name] = vdom.props[name]);
        vdom.props.children.forEach(child => {
            render(child, dom);
        })
        container.appendChild(dom)
    }

    // container.innerHTML = `<pre>${JSON.stringify(vdom,null,2)}</pre>`
}
export default {
    createElement,
    render
}

初级版

function createElement(type, props, ...children) {
    return {
        type,
        props: {
            ...props,
            children: children.map(child => {
                return typeof child === 'object' ? child : createTextElement(child);
            })
        }
    }
}

function createTextElement(text) {
    return {
        type: 'text',
        props: {
            nodeValue: text,
            children: []
        }
    }
}
function render(vdom, container) {
    nextUnitOfWork = {
        dom: container,
        props: {
            children: [vdom]
        },  
    }
    // container.innerHTML = `<pre>${JSON.stringify(vdom,null,2)}</pre>`
}

let nextUnitOfWork = null;

// render初始化第一个任务
function workLoop() {
    // 有下一个任务,当前帧未结束
    while (nextUnitOfWork) {
        nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
    }    
    // while (false) {
    // }
    requestIdleCallback(workLoop)
}

// 启动空闲时间处理

requestIdleCallback(workLoop)

// add to dom
// create fibers for children
// return next unit of work
function performUnitOfWork(fiber) {
    // 根据当前任务获取下一个
    if (!fiber.dom) {
        fiber.dom = createDom(fiber);
    }
    if (fiber.return) {
        fiber.return.dom.appendChild(fiber.dom);
    }

    // 递归处理子元素,给子元素创建fiber
    const elements = fiber.props.children;
    let index = 0;
    let prevSibling = null;
    while (index < elements.length) {
        let element = elements[index];
        const newFiber = {
            type: element.type,
            props: element.props,
            return: fiber,
            dom: null
        }
        if (index === 0) {
            fiber.child = newFiber
        } else {
            prevSibling.sibling = newFiber;
        }
        prevSibling = newFiber;
        index++;
        //fiber基本结构构建完成
    }

    if (fiber.child) {
        return fiber.child
    }
    let nextFiber = fiber;
    while (nextFiber) {
        if (nextFiber.sibling) {
            return nextFiber.sibling
        }
        nextFiber = nextFiber.return;
    }

}

function createDom(vdom) {
    const dom = vdom.type === 'text' ? document.createTextNode('') :
        document.createElement(vdom.type);
    Object.keys(vdom.props)
        .filter(key => key !== 'children')
        .forEach(name => dom[name] = vdom.props[name]);
    return dom;
}

export default {
    createElement,
    render
}

进阶版

/**
 * 为什么要有虚拟dom,原生的dom上挂有非常多的属性,浏览器原生操作dom性能开销很大
 * 使用js对象来描述dom,有任何频繁的操作不去操作dom而是操作对象,会后统一尽可能少
 * 地去操作实际的dom
 */

/**
 * 1.workLoop不断循环调用,处理
 * nextUnitOfWork(不停遍历fiber tree,最后变成null,初始值为workInProgressRoot)
 * workInProgressRoot (fibre tree的根节点,nextUnitOfWork变null时,
 * 表示一次构建fiber tree完成,commitRoot会统一提交,生成真实dom)
 */

/**
 * 2.render 初始化nextUnitOfWork 和 workInProgressRoot
 * workLoop观察到nextUnitOfWork有值,开始进行深度遍历,执行performUnitOfWork
 */

//  workInProgressRoot = {
//     dom: container,
//     props: {
//         children: [vdom]
//     },
//     base: currentRoot
// }

/**
 * 3.判断fiber是函数组件还是类组件,先考虑类组件
 * updateHostComponent
 * fiber.dom不存在,即不是入口,需要给fiber创建dom,添加事件 fiber.dom = createDom(fiber)->updateDom(fiber,{},fiber.props);
 * reconcileChildren(fiber,elements); elements = fiber.props.children 给fiber添加子节点,给fiber添加tag,把子节点连起来并指向父节点
 * !如果fiber存在子节点,则返回fiber子节点
 * !如果fiber存在兄弟结点,则返回fiber兄弟结点
 * !如果fiber的父节点存在兄弟结点,则返回fiber父节点的兄弟结点,返回值给到nextUnitOfWork
 */

/**
 * 3.1 reconcileChildren遍历fiber tree,更新 todo 添加tag的代码有问题
 */

/**
 * 4.commit阶段,commitRoot(),nextUnitOfWork已经完成fiber tree的构建,
 * workInProgressRoot根节点还在
 * 删除deletions中存的结点forEach(commitWorker)
 * commitWorker(workInprogressRoot.children)真正执行dom渲染
 * 重置双缓存指针指向
 * currentRoot = workInProgressRoot
 * workInProgressRoot = null
 */

/**
 * 4.1对函数组件return上可能不存在父节点,要循环找到父节点
 * 根据fiber.effectTag,判断如何处理fiber
 * 1.updateDom(fiber.dom,fiber.base.props,fiber.props)
 * 2.replace parentDom.append(fiber.dom)
 * 3.deletion commitDeletion(fiber,parentDom)=>fiber.dom若不存在会递归调用,函数组件的逻辑
 */

/**
 * 补充:updateDom(dom,prevProps,nextProps)
 * 1.删除调prevProps中存在nextProps中不存在的属性,移除时间
 * 2.nextProps中的属性和事件全部添加
 */

/**
 * 全局变量
 * nextUnitOfWork
 * workInProgressRoot
 * currentRoot
 * deletions
 */

/**
 * 入口1:requestIdleCallback(workLoop),循环调用
 * 入口2:render,初始化触发workLoop 中 performUnitOfWork构建新的fiber tree
 * 待添加:setState会重置nextUnitOfWork和workInProgressRoot,引起fiber tree重新执行
 */

/**
 * vdom生成后,在render中构建fiber tree
 * fiber tree为链表结构,调用render,把vdom转化成fiber tree,(之前的 x 错误 x 理解,xxx fiber tree是改变了之前生成vdom方式 xxx);
 * vdom形式还是未变
 */

// render会递归创建vdom,如果vdom变得很庞大,render的遍历也就会非常耗时,
// 利用浏览器api requestIdleCallback ,让浏览器在空闲时执行diff任务,
// 动画交互等会先执行
/**
 * 异步任务调度逻辑
 */

// 下一个单元任务
// render会初始化第一个任务
let nextUnitOfWork = null;
let workInProgressRoot = null;
let currentRoot = null;
let deletions = null;

/**
 * 遍历对象,append创建真实dom
 * @param {虚拟dom} vdom 
 * @param {容器} container 
 */
function render(vdom, container) {
    /**
     * 这个就是fiber
     */
    workInProgressRoot = {
        dom: container,
        props: {
            children: [vdom]
        },
        base: currentRoot
    }
    nextUnitOfWork = workInProgressRoot;
    // container.innerHTML = `<pre>${JSON.stringify(vdom, null, 2)}</pre>`
}

// 调度diff或者渲染任务
function workLoop(deadline) {
    while (nextUnitOfWork && deadline.timeRemaining() > 1) {
        nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
    if (!nextUnitOfWork && workInProgressRoot) {
        // 没有任务了 并且根节点还在
        commitRoot();
    }
    requestIdleCallback(workLoop)
}
// 启动空闲时间处理
requestIdleCallback(workLoop);

/**
 * !!重要 构建fiber结构
 * @param {} fiber 
 */
function performUnitOfWork(fiber) {
    const ifFunctionComponent = fiber.type instanceof Function;
    if (ifFunctionComponent) {
        updateFunctionComponent(fiber);
    } else {
        // dom
        updateHostComponent(fiber);
    }

    function updateFunctionComponent(fiber) {
        const children = [fiber.type(fiber.props)];
        reconcileChildren(fiber, children);
    }

    function updateHostComponent(fiber) {
        // 根据当前任务,获取下一个任务
        if (!fiber.dom) {
            // 不是入口
            fiber.dom = createDom(fiber)
        }

        //真实的dom操作
        // if (fiber.return) {
        //     fiber.return.dom.append(fiber.dom)
        // }

        // 将前面的vdom转换成fiber结构
        const elements = fiber.props.children;
        // 调和子元素
        reconcileChildren(fiber, elements);
    }

    // 这个函数返回的下一个任务,当前fiber的子节点
    // 或者兄弟结点,
    // 或者父元素的下一个兄弟结点,
    // 最后到根节点结束
    if (fiber.child) {
        return fiber.child
    }

    let nextFiber = fiber;
    while (nextFiber) {
        if (nextFiber.sibling) {
            return nextFiber.sibling;
        }

        nextFiber = nextFiber.return;
    }
}

/**
 * 为了fiber随时可以调度,把创建dom的逻辑提取出来,不再通过递归render
 * 根据vdom创建真实dom挂到fiber上
 * @param {虚拟dom}} vdom 
 */
function createDom(vdom) {
    const dom = vdom.type === 'text' ? document.createTextNode('') :
        document.createElement(vdom.type);
    // Object.keys(vdom.props)
    //     .filter(key => key !== 'children')
    //     .forEach(name => {
    //         /**
    //          * todo 事件处理,属性兼容
    //          */
    //         dom[name] = vdom.props[name]
    //     });
    updateDom(dom, {}, vdom.props)
    return dom;
}

function updateDom(dom, prevProps, nextProps) {
    // 规避child属性
    // 老的存在,取消
    // 新的存在,新增
    Object.keys(prevProps)
        .filter(name => name !== 'children')
        .filter(name => !(name in nextProps))
        .forEach(name => {
            if (name.slice(0, 2) === 'on') {
                dom.removeEventListener(name.slice(2).toLocaleLowerCase(), prevProps[name], false)
            } else {
                dom[name] = ''
            }
        })
    // if(!nextProps){
    //     return 
    // }
    Object.keys(nextProps)
        .filter(name => name !== 'children')
        .forEach(name => {
            if (name.slice(0, 2) === 'on') {
                // console.log(nextProps[name],dom,name.slice(2))
                // dom[name] = nextProps[name]
                dom.addEventListener(name.slice(2).toLocaleLowerCase(), nextProps[name], false)
            } else {
                dom[name] = nextProps[name];
            }
        })
}

function reconcileChildren(workInProgressFiber, elements) {
    // 构建fiber结构
    let index = 0;
    let prevSibling = null;
    // let next = beginWork(fiber)
    let oldFiber = workInProgressFiber.base && workInProgressFiber.base.child;
    while (index < elements.length 
        // && oldFiber !== null
        ) {
        const element = elements[index];

        let newFiber = null;
        const sameType = oldFiber && element && oldFiber.type === element.type;

        // 复用节点,更新
        if (sameType) {
            // props是新的
            newFiber = {
                type: oldFiber.type,
                props: element.props,
                dom: oldFiber.dom,
                return: workInProgressFiber,
                base: oldFiber,
                effectTag: 'UPDATE',
            }
        }

        // 新增节点
        else if (!sameType && element) {
            newFiber = {
                type: element.type,
                props: element.props,
                dom: null,
                return: workInProgressFiber,
                base: null,
                effectTag: 'PLACEMENT'
            }
        }

        // 删除节点
        else if (!sameType && oldFiber) {
            oldFiber.effectTag = 'DELETION';
            deletions.push(oldFiber);
        }

        if (oldFiber) {
            oldFiber = oldFiber.sibling;
        }
        if (index === 0) {
            workInProgressFiber.child = newFiber;
        } else {
            prevSibling.sibling = newFiber;
        }
        prevSibling = newFiber;
        index++
    }

}

// workInProgressRoot指向fiber的根节点
function commitRoot() {
    deletions && deletions.forEach(commitWorker);
    commitWorker(workInProgressRoot.child);
    console.log(workInProgressRoot)
    currentRoot = workInProgressRoot;
    workInProgressRoot = null;
}

function commitWorker(fiber) {
    if (!fiber) {
        return
    }
    // const domParent = fiber.return.dom;
    let domParentFiber = fiber.return;
    while (!domParentFiber.dom) {
        domParentFiber = domParentFiber.return;
    }

    const domParent = domParentFiber.dom;
    if (fiber.effectTag === 'UPDATE' && fiber.dom !== null) {
        updateDom(fiber.dom, fiber.base.props, fiber.props);
    } else if (fiber.effectTag === 'PLACEMENT') {
        domParent.append(fiber.dom)
    } else if (fiber.effectTag === 'DELETION' && fiber.dom !== null) {
        commitDeletion(fiber, domParent);
        // domParent.removeChild(fiber.dom)
    }
    commitWorker(fiber.child);
    commitWorker(fiber.sibling);
}

function commitDeletion(fiber, domParent) {
    if (fiber.dom) {
        domParent.removeChild(fiber.dom)
    } else {
        commitDeletion(fiber.child, domParent);
    }
}

/**
 * createElement返回虚拟dom
 * 1.参数 type,props,children
 */
function createElement(type, props, ...children) {
    return {
        type,
        props: {
            ...props,
            children: children.map(child => {
                return typeof child === 'object' ? child : createTextElement(child)
            })
        },
    }
}
function createTextElement(text) {
    return {
        type: 'text',
        props: {
            nodeValue: text,
            children: []
        }
    }
}

/**
 * fiber架构实现
 * vdom之前是树,fiber是用双向链表实现
 * 
 */

// fiber = {
//     dom:真实dom,
//     return:父亲,
//     child:第一个子元素,
//     slibing:兄弟
// }
/**
 * 提交commit,因为渲染过程中如果被打断
 * ui会变得很奇怪,所以应该把dom操作独立出来,
 * 用一个全局变量存储正在工作的fiber根节点
 * WorkInprogress tree
 * 在fiber构建完成后统一操作dom,
 * 构建计算的过程可以被中断,但操作dom的过程需要一气呵成
 */

export default {
    createElement,
    render
}