一、简介

本文次要了解fiber的基本原理。为了可能更好的了解fiber原理,咱们会从零开始构建一个简略的react,并在其中引入fiber以及useState hookDOM-DIFF等。

二、React根底

① JSX语法
JSX 是 JavaScript 的一种语法扩大,它和模板语言很靠近,然而它充沛具备 JavaScript 的能力。所以无奈像JavaScript一样被间接执行

// JSXlet age = 18;let element = (    <div>        <h1>age: {age}</h1>        <button onClick={() => alert(18)}>批改age的值</button>    </div>);

下面这段JSX看起来十分像HTML,然而它又不仅仅是HTML,其具备肯定的JavaScript能力,比方能够通过{}去读取变量,以及进行一些运算等等。
下面这段JSX会被放到.js文件中,如果咱们间接执行,那么会报错。而在React中之所以可能执行,是因为借助了Babel的转换,最终会被解析成JS成一段JS。下面这段代码会被解析为,如下所示

let age = 18;let element = React.createElement("div", {},     React.createElement("h1", {}, "age: ", age),    React.createElement("button", {        onClick: function onClick() {            return alert(18);        }    }, "批改age的值"));

所以React只有提供createElement()办法就能够正确解析下面这段JSX了。

② 实现createElement()办法
createElement()办法次要负责接管JSX被Babel解析后传递过去的参数,而其次要作用就是解析参数并最终返回一个虚构DOM对象

function createElement(type, config, ...children) { // Babel解析后传递的config必定是一个对象,不论JSX中有没有设置属性    delete config.__source; // ——souce属性和__self属性太简单间接移除    delete config.__self;    const props = {...config};    props.children = children.map((child) => { // 遍历子节点,次要解决一些纯文本        return typeof child === "object" ? child : createTextElement(child)    });    return { // 结构一个虚构DOM节点        type,        props    }}function createTextElement(text) { // 专门解决纯文本,将纯文本转化成一个React虚构DOM节点    return {        type: "TEXT",        props: {            nodeValue: text,            children: []        }    }}

通过createElement()办法的解决,就能将JSX转换为对应的虚构DOM节点。

③ 实现render()办法
render(vdom, container)办法会接管一个虚构DOM对象和一个实在的容器DOM作为虚构DOM渲染实现后的挂载点。其次要作用就是将虚构DOM渲染为实在DOM并挂载到容器下

function render(vdom, container) {    let dom;    let props = vdom.props;    if (vdom.type === "TEXT") { // 是文本节点        dom = document.createTextNode("");    } else {        dom = document.createElement(vdom.type);    }    updateProperties(dom, props); // 更新DOM属性    vdom.props.children.forEach((child) => { // 遍历子节点,递归调用render()办法将子节点渲染进去        render(child, dom);    });    container.appendChild(dom); // 整个虚构DOM渲染实现后将其退出到容器中进行挂载到页面上}function updateProperties(dom, props) {    for (let key in props) { // 遍历属性并更新到DOM节点上        if (key !== "children") {            if (key.slice(0, 2) === "on") {                dom.addEventListener(key.slice(2).toLowerCase(), props[key], false);            } else {                dom[key] = props[key];            }        }    }}

三、Fiber简介

Fiber在英文中的意思为“纤维化”,即细化,将工作进行细化。咱们能够把一个耗时长的工作分成很多小片,每一个小片的运行工夫很短,尽管总工夫仍然很长,然而在每个小片执行完之后,都给其余工作一个执行的机会,这样惟一的线程就不会被独占,其余工作仍然有运行的机会。React中的Fiber就把整个更新过程碎片化
下面咱们的render()办法在更新的时候是进行递归操作的,如果在更新的过程中有大量的节点须要更新,就会呈现长时间占用JS主线程的状况,并且整个递归过程是无奈被打断的,因为JS线程和GUI线程是互斥的,所以可能会看到UI卡顿。

所以要实现Fiber架构,必须要解决两个问题:
a. 保障工作在浏览器闲暇的时候执行;
b. 将工作进行碎片化;

① requestIdleCallback
requestIdleCallback(callback)是实验性API,能够传入一个回调函数,回调函数可能收到一个deadline对象,通过该对象的timeRemaining()办法能够获取到以后浏览器的闲暇工夫,如果有闲暇工夫,那么就执行一小段工作,如果工夫有余了,则持续requestIdleCallback,等到浏览器又有闲暇工夫的时候再接着执行。

② 链表构造
目前的虚构DOM是树结构,当工作被打断后,树结构无奈复原之前的工作继续执行,所以须要一种新的数据结构,即链表,链表能够蕴含多个指针,能够轻易找到下一个节点,继而复原工作的执行。Fiber采纳的链表中蕴含三个指针,parent指向其父Fiber节点child指向其子Fiber节点sibling指向其兄弟Fiber节点。一个Fiber节点对应一个工作节点。

四、实现Fiber

① 保障工作在浏览器闲暇的时候执行
定义一个工作循环回调函数,并通过requestIdleCallback注册,通过判断以后Fiber是否存在以及浏览器是否有闲暇工夫来判断是否须要打断工作执行。

// 新增如下代码let currentFiber; // 保留当前工作的Fiber节点let rootFiber; // 根Fiber节点,render()办法内会进行初始化function commitRoot() {}function workLoop(deadline) {    while(currentFiber && deadline.timeRemaining() > 0) {        currentFiber = getNextFiber(currentFiber);    }    if (!currentFiber && rootFiber) { // 如果没有工作了,则一次性提交整个根fiber,渲染出界面        commitRoot(); // 提交根Fiber节点开始渲染到界面    }    requestIdleCallback(workLoop); // 如果还有工作, 然而闲暇工夫不够了,则持续注册回调,等浏览器闲暇之后再复原执行}requestIdleCallback(workLoop);function getNextFiber(fiber) {}

② 启动工作
目前咱们注册了workLoop回调,其执行条件是currentFiber必须存在,所以要启动工作,必须初始化currentFiber,render()办法中将不再递归遍历虚构DOM,创立实在DOM并退出到容器中,咱们须要在render()中初始化一个rootFiber并把rootFiber当作是currentFiber

function render(vdom, container) {    rootFiber = { // 渲染的时候创立一个根fiber        type: "root",        props: {            children: [vdom] // children存储对应的虚构DOM节点        },        dom: container, // rootFiber节点对应的dom为容器        parent: null, // 指向父Fiber        child: null, // 指向子Fiber        sibling: null // 指向兄弟Fiber    };    currentFiber = rootFiber; // 将根fiber节点标记为currentFiber,开始一个一个节点进行渲染}

Fiber节点和虚构DOM节点十分类似,只是比虚构DOM多了一些属性

③ 实现获取下一个Fiber的逻辑
a. 首先看一下fiber对应的dom有没有创立进去,如果没有则创立出对应的实在DOM
b. 取出fiber的虚构子节点并遍历,遍历的过程中开始层层构建fiber链表将第一个子节点作为以后fiber的child将第二个子节点作为第一个子节点的sibling,以此类推,将第三个子节点作为第二个子节点的sibling,最终构建出下一层Fiber链表。
c. 下一层Fiber链表构建实现后,就能够找到下一个Fiber节点,即下一个工作了,获取下一个工作的时候,首先会把以后Fiber的child指向的fiber作为下一个工作,如果以后Fiber没有child,那么就获取其sibling,如果以后Fiber也没有sibling了,那么就找到其parentFiber再通过parentFiber找到其sibling作为下一个工作

// 这里须要给Fiber节点创立出对应的实在DOM,所以将创立DOM的办法抽取进去function createDOM(vdom) {    let dom;    if (vdom.type === "TEXT") {        dom = document.createTextNode("");    } else {        dom = document.createElement(vdom.type);    }    updateProperties(dom, vdom.props);    return dom;}
function getNextFiber(fiber) {    if (!fiber.dom) { // 刚开渲染的时候,只有根fiber才有对应的dom,即容器        fiber.dom = createDOM(fiber);    }    const vchildren = (fiber.props && fiber.props.children) || []; // 取出以后fiber的子节点,即子虚构DOM    createFiberLinkedList(fiber, vchildren);    if (fiber.child) { // 如果以后fiber存在child则取出其child指向的Fiber节点作为下一个工作        return fiber.child;    }    // 当遍历到最底层的时候,就会呈现没有child的状况,此时就要开始找其兄弟节点了    let currentFiber = fiber;    while(currentFiber) {        if (currentFiber.sibling) { // 如果存在兄弟节点            return currentFiber.sibling; // 返回其兄弟节点        }        // 当遍历到最初一个子节点的时候,会呈现sibling兄弟节点不存在的状况        currentFiber = currentFiber.parent; // 找到以后节点的父节点    }}function createFiberLinkedList(fiber, vchildren) {    let index = 0;    let prevSibling = null; // 兄弟节点的上一个节点    while(index < vchildren.length) { // 遍历子节点,进行层层构建        let vchild = vchildren[index];        let newFiber = { // 依据子节点结构出对应的Fiber节点            type: vchild.type, // 以后节点类型 h1            props: vchild.props, // 以后节点属性            parent: fiber, // 指向父节点            dom: null,            child: null        }        if (index === 0) { // 如果是第一个子节点,则作为以后fiber的子fiber            fiber.child = newFiber;        } else {            prevSibling.sibling = newFiber; // 将第二个以及之后的子节点作为上一个兄弟节点的兄弟节点        }        prevSibling = newFiber; // 将以后创立的fiber保留为其兄弟节点的上一个节点        index++;    }}

④ 提交整个根Fiber并渲染出界面
当初根Fiber曾经构建实现,接下来就是将整个根Fiber进行提交,而后渲染出界面,提交的时候须要将整个根Fiber重置为null防止屡次提交。提交的时候首先从根Fiber的child开始找到其父Fiber对应的DOM,而后将子Fiber对应的DOM退出到父Fiber对应的DOM中,接着反复该过程递归将以后Fiber的child和sibling进行提交

function commitRoot() {    commitWorker(rootFiber.child); // 将根fiber的子fiber传入    rootFiber = null;}function commitWorker(fiber) {    if (!fiber) {        return;    }    const parentDOM = fiber.parent.dom; // 拿到以后fiber的父fiber对应的dom    parentDOM.appendChild(fiber.dom); // 将以后fiber对应的dom退出父dom中    commitWorker(fiber.child); // 递归将以后fiber的子fiber提交    commitWorker(fiber.sibling); // 递归将以后fiber的下一个兄弟fiber提交}

五、反对函数组件

函数组件的type比拟非凡是一个函数,无奈像HTML标签一样,间接通过document.createElement()办法创立出对应的DOM元素,所以在getNextFiber()的时候,必须先判断是否是函数组件,如果是函数组件,那么不创立DOM,而是执行函数组件获取到其返回的虚构节点作为函数组件对应Fiber的子节点

function doFunctionComponent(fiber) {    const vchildren = [fiber.type(fiber.props)]; // 执行函数组件拿到对应的虚构DOM作为函数组件的虚构子节点    createFiberLinkedList(fiber, vchildren);}function doNativeDOMComponent(fiber) {    if (!fiber.dom) { // 刚开渲染的时候,只有根fiber才有对应的dom,即容器        fiber.dom = createDOM(fiber);    }    const vchildren = (fiber.props && fiber.props.children) || []; // 子节点就是虚构DOM    createFiberLinkedList(fiber, vchildren);}function getNextFiber(fiber) {    const isFunctionComponent = typeof fiber.type === "function";    if (isFunctionComponent) { // 处理函数组件        doFunctionComponent(fiber);    } else { // 解决DOM        doNativeDOMComponent(fiber);    }    ......}

新增了函数组件对应的Fiber节点后,在提交的时候会存在一些问题:
a. 因为函数组件对应的Fiber没有对应的实在DOM,所以无奈间接通过其父Fiber的DOM将其退出
b. 因为函数组件对应的Fiber没有对应的实在DOM,所以其子Fiber节点也无奈通过其父Fiber(函数组件对应的Fiber)获取到DOM并退出子Fiber节点对应的DOM

function commitWorker(fiber) {    ......    // const parentDOM = fiber.parent.dom; // 拿到以后fiber的父fiber对应的dom    let parentFiber = fiber.parent; // 获取到以后Fiber的父Fiber    while (!parentFiber.dom) { // 看看父Fiber有没有DOM,如果没有阐明是函数组件对应的Fiber,须要持续向上获取其父Fiber节点,直到其父Fiber有DOM为止        parentFiber = parentFiber.parent;    }    const parentDOM = parentFiber.dom; // 获取到父Fiber对应的实在DOM    // parentDOM.appendChild(fiber.dom); // 将以后fiber对应的dom退出父dom中    if (fiber.dom) { // 如果以后fiber存在dom则退出到父节点中, 函数组件没有对应dom        parentDOM.appendChild(fiber.dom); // 将以后fiber对应的dom退出父dom中    }    ......}

六、实现useState hook

useState能够让咱们的函数组件领有本人的状态。useState的实现非常简单,首先在创立函数组件对应Fiber的时候,会给其增加一个hooks数组,用于存储以后函数组件内创立的hook。每当执行useState()函数的时候,外部会创立一个hook对象,该hook对象蕴含了以后的state和批改时传入的最新状态数组queue,同时返回一个setState()函数,而后将创立的hook放到函数组件对应Fiber的hooks数组内,当setState(newState)函数被调用的时候,传入的最新状态就会被放到hook的queue数组中,同时更新rootFiber和currentFiber,以便让workLoop能够继续执行,此时整个组件将会被从新渲染,当函数组件从新渲染的时候,useState()也会从新执行,此时会通过上一次渲染时函数组件对应的Fiber拿到上一次的hook,继而从hook的queue中取出调用setState时候传入的最新状态数据,而后更新为以后hook的状态,从而使状态失去更新。

let functionComponentFiber; // 保留函数组件对应的Fiberlet hookIndex; // 可能一个组件中应用到多个hook,记录hook的索引let oldRootFiber; // rootFiber提交完后保留为旧的rootFiber即上一次渲染的rootFiberfunction doFunctionComponent(fiber) {    functionComponentFiber = fiber; // 保留函数组件对应的Fiber节点    hookIndex = 0; // 初始化hook索引为0    functionComponentFiber.hooks = []; // 并在函数组件对应的fiber上增加一个hooks数组,每次从新渲染都会从新初始化为空数组    ......}function useState(init) {    // 从上一次渲染实现的函数组件对应的fiber的hooks数组中依据索引获取到对应的hook    const oldHook = functionComponentFiber.base && functionComponentFiber.base.hooks && functionComponentFiber.base.hooks[hookIndex];    const hook = { // 创立一个新的hook,state从上次中获取        state: oldHook? oldHook.state: init,        queue: []    };    const newStates = oldHook ? oldHook.queue : []; // 从上次hook中获取最新的状态    newStates.forEach(newState => {        hook.state = newState; // 更新hook    });    const setState = (newState) => {        hook.queue.push(newState); // 将新的状态放到hook的queue数组中        rootFiber = {            dom: oldRootFiber.dom,            props: oldRootFiber.props,            base: oldRootFiber        }        currentFiber = rootFiber;    }    functionComponentFiber.hooks.push(hook); // 将以后hook保留到函数组件对应的fiber节点的hooks数组中    hookIndex++; // 可能会有多个状态    return [hook.state, setState];}

在useState()中须要获取上一次的hook,所以须要给Fiber节点减少一个base属性用于保留上一次的Fiber节点

function createFiberLinkedList(fiber, vchildren) {    ......    let oldFiber = fiber.base && fiber.base.child; // 取出第一个子节点对应的oldFiber    ......    while(index < vchildren.length) { // 遍历子节点,进行层层构建        ......        let newFiber = { // 依据子节点结构出对应的Fiber节点            type: vchild.type, // 以后节点类型 h1            props: vchild.props, // 以后节点属性            parent: fiber, // 指向父节点            dom: null,            child: null,            base: oldFiber // 保留上一次的Fiber        }        // 如果比拟的时候有多个子节点,须要更新oldFiber        if (oldFiber) {            oldFiber = oldFiber.sibling;        }        ......    }}

此时还有一个问题,就是从新渲染的时候,必须将容器DOM中上一次渲染的DOM清空,否则会从新创立一份DOM追加到容器DOM中。

function commitRoot() {    let rootDOM = rootFiber.dom; // 取出容器DOM    while(rootDOM.hasChildNodes()) {        rootDOM.removeChild(rootDOM.firstChild); // 清空容器DOM中的子节点    }    ......}

七、实现DOM-DIFF

DOM-DIFF次要就是比拟两个Fiber节点的type是否统一,如果统一则进行复用上一次渲染的DOM节点,而后更新DOM的属性即可。

function createFiberLinkedList(fiber, vchildren) {    ......    while(index < vchildren.length) { // 遍历子节点,进行层层构建        let newFiber;        const sameType = oldFiber && vchild && oldFiber.type === vchild.type; // 比拟新旧Fiber的type是否统一        if (sameType) { // 示意是更新            newFiber = {                type: oldFiber.type,                props: vchild.props, // 从最新的虚构节点中获取最新的属性                dom: oldFiber.dom, // 应用上次创立的dom即可                parent: fiber,                child: null,                base: oldFiber,                effectTag: "UPDATE" // 标识为更新            }        }        if (!sameType && vchild) { // 如果类型不同且存在虚构子节点则示意新增            newFiber = {                type: vchild.type, // 以后节点类型 如h1                props: vchild.props, // 以后节点属性                parent: fiber, // 指向父节点                dom: null,                child: null,                base: null,                effectTag: "ADD" // 标识为新增            }        }    }}

因为进行了DOM的复用,所以在提交DOM的时候,就不必先将容器DOM的所有子节点清空了,如:

function commitRoot() {    // 不须要先清空容器DOM的所有子节点了    // let rootDOM = rootFiber.dom;    // while(rootDOM.hasChildNodes()) {    //     rootDOM.removeChild(rootDOM.firstChild);    // }    ......}function commitWorker(fiber) {    if (fiber.effectTag === "ADD" && fiber.dom != null) { // 新增        parentDOM.appendChild(fiber.dom); // 将以后fiber对应的dom退出父dom中    } else if (fiber.effectTag === "UPDATE" && fiber.dom !== null) { // 更新        updateProperties(fiber.dom, fiber.base.props, fiber.props);    }}

复用之前的DOM时,须要更新DOM上的属性,所以须要批改updateProperties()办法,传入新的和旧的属性,如:

function updateProperties(dom, oldProps, newProps) {    for (let key in oldProps) {        if (!newProps[key]) {            dom.removeAttribute(key);        }    }    for (let key in newProps) { // 遍历属性并更新到DOM节点上        if (key !== "children") {            if (key.slice(0, 2) === "on") {                dom.addEventListener(key.slice(2).toLowerCase(), newProps[key], false);            } else {                if (oldProps[key] !== newProps[key]) { // 如果属性值发生变化则进行更新                    dom[key] = newProps[key];                }            }        }    }}

八、残缺代码如下

react.js代码如下:

let currentFiber; // 保留当前工作的Fiber节点let rootFiber; // 根Fiber节点,render()办法内会进行初始化let functionComponentFiber; // 保留函数组件对应的Fiberlet hookIndex; // 可能一个组件中应用到多个hook,记录hook的索引let oldRootFiber; // rootFiber提交完后保留为旧的rootFiber即上一次渲染的rootFiberfunction createElement(type, config, ...children) { // Babel解析后传递的config必定是一个对象,不论JSX中有没有设置属性    delete config.__source; // ——souce属性和__self属性太简单间接移除    delete config.__self;    const props = {...config};    props.children = children.map((child) => { // 遍历子节点,次要解决一些纯文本        return typeof child === "object" ? child : createTextElement(child)    });    return { // 结构一个虚构DOM节点        type,        props    }}function createTextElement(text) { // 专门解决纯文本,将纯文本转化成一个React虚构DOM节点    return {        type: "TEXT",        props: {            nodeValue: text,            children: []        }    }}function render(vdom, container) {    // let dom;    // let props = vdom.props;    // if (vdom.type === "TEXT") { // 是文本节点    //     dom = document.createTextNode("");    // } else {    //     dom = document.createElement(vdom.type);    // }    // updateProperties(dom, props); // 更新DOM属性    // vdom.props.children.forEach((child) => { // 遍历子节点,递归调用render()办法将子节点渲染进去    //     render(child, dom);    // });    // container.appendChild(dom); // 整个虚构DOM渲染实现后将其退出到容器中进行挂载到页面上    rootFiber = { // 渲染的时候创立一个根fiber        type: "root",        props: {            children: [vdom] // children存储对应的虚构DOM节点        },        dom: container, // rootFiber节点对应的dom为容器        parent: null, // 指向父Fiber        child: null, // 指向子Fiber        sibling: null // 指向兄弟Fiber    };    currentFiber = rootFiber; // 将根fiber节点标记为currentFiber,开始一个一个节点进行渲染}function createDOM(vdom) {    let dom;    if (vdom.type === "TEXT") {        dom = document.createTextNode("");    } else {        dom = document.createElement(vdom.type);    }    updateProperties(dom, {}, vdom.props);    return dom;}function updateProperties(dom, oldProps, newProps) {    for (let key in oldProps) {        if (!newProps[key]) {            dom.removeAttribute(key);        }    }    for (let key in newProps) { // 遍历属性并更新到DOM节点上        if (key !== "children") {            if (key.slice(0, 2) === "on") {                dom.addEventListener(key.slice(2).toLowerCase(), newProps[key], false);            } else {                if (oldProps[key] !== newProps[key]) { // 如果属性值发生变化则进行更新                    dom[key] = newProps[key];                }            }        }    }}function commitRoot() {    // 不须要先清空容器DOM的所有子节点了    // let rootDOM = rootFiber.dom;    // while(rootDOM.hasChildNodes()) {    //     rootDOM.removeChild(rootDOM.firstChild);    // }    commitWorker(rootFiber.child); // 将根fiber的子fiber传入    oldRootFiber = rootFiber; // 提交后将最终的rootFiber保留为oldRootFiber    rootFiber = null;}function commitWorker(fiber) {    if (!fiber) {        return;    }    let parentFiber = fiber.parent;    while (!parentFiber.dom) {        parentFiber = parentFiber.parent;    }    const parentDOM = parentFiber.dom;    // const parentDOM = fiber.parent.dom; // 拿到以后fiber的父fiber对应的dom    // if (fiber.dom) { // 如果以后fiber存在dom则退出到父节点中, 函数组件没有对应dom    //     parentDOM.appendChild(fiber.dom); // 将以后fiber对应的dom退出父dom中    // }    if (fiber.effectTag === "ADD" && fiber.dom != null) {        parentDOM.appendChild(fiber.dom); // 将以后fiber对应的dom退出父dom中    } else if (fiber.effectTag === "UPDATE" && fiber.dom !== null) {        updateProperties(fiber.dom, fiber.base.props, fiber.props);    }    // parentDOM.appendChild(fiber.dom); // 将以后fiber对应的dom退出父dom中    commitWorker(fiber.child); // 递归将以后fiber的子fiber提交    commitWorker(fiber.sibling); // 递归将以后fiber的下一个兄弟fiber提交}function workLoop(deadline) {    while(currentFiber && deadline.timeRemaining() > 0) {        currentFiber = getNextFiber(currentFiber);    }    if (!currentFiber && rootFiber) { // 如果没有工作了,则一次性提交整个根fiber,渲染出界面        commitRoot(); // 提交根Fiber节点开始渲染到界面    }    requestIdleCallback(workLoop); // 如果还有工作, 然而闲暇工夫不够了,则持续注册回调,等浏览器闲暇之后再复原执行}requestIdleCallback(workLoop);function doNativeDOMComponent(fiber) {    if (!fiber.dom) { // 刚开渲染的时候,只有根fiber才有对应的dom,即容器        fiber.dom = createDOM(fiber);    }    const vchildren = (fiber.props && fiber.props.children) || []; // 子节点就是虚构DOM    createFiberLinkedList(fiber, vchildren);}function doFunctionComponent(fiber) {    functionComponentFiber = fiber;    hookIndex = 0;    functionComponentFiber.hooks = []; // 并在函数组件对应的fiber上增加一个hooks数组,每次从新渲染都会从新初始化为空数组    const vchildren = [fiber.type(fiber.props)]; // 执行函数组件拿到对应的虚构DOM作为函数组件的虚构子节点    createFiberLinkedList(fiber, vchildren);}function getNextFiber(fiber) {    const isFunctionComponent = typeof fiber.type === "function";    if (isFunctionComponent) {        doFunctionComponent(fiber);    } else {        doNativeDOMComponent(fiber);    }    if (fiber.child) { // 如果以后fiber存在child则取出其child指向的Fiber节点作为下一个工作        return fiber.child;    }    // 当遍历到最底层的时候,就会呈现没有child的状况,此时就要开始找其兄弟节点了    let currentFiber = fiber;    while(currentFiber) {        if (currentFiber.sibling) { // 如果存在兄弟节点            return currentFiber.sibling; // 返回其兄弟节点        }        // 当遍历到最初一个子节点的时候,会呈现sibling兄弟节点不存在的状况        currentFiber = currentFiber.parent; // 找到以后节点的父节点    }}function createFiberLinkedList(fiber, vchildren) {    let index = 0;    let oldFiber = fiber.base && fiber.base.child; // 取出第一个子节点对应的oldFiber    let prevSibling = null; // 兄弟节点的上一个节点    while(index < vchildren.length) { // 遍历子节点,进行层层构建        let vchild = vchildren[index];        let newFiber;        const sameType = oldFiber && vchild && oldFiber.type === vchild.type; // 比拟新旧Fiber的type是否统一        if (sameType) { // 示意是更新            newFiber = {                type: oldFiber.type,                props: vchild.props, // 从最新的虚构节点中获取最新的属性                dom: oldFiber.dom, // 应用上次创立的dom即可                parent: fiber,                child: null,                base: oldFiber,                effectTag: "UPDATE" // 标识为更新            }        }        if (!sameType && vchild) { // 如果类型不同且存在虚构子节点则示意新增            newFiber = {                type: vchild.type, // 以后节点类型 如h1                props: vchild.props, // 以后节点属性                parent: fiber, // 指向父节点                dom: null,                child: null,                base: null,                effectTag: "ADD" // 标识为新增            }        }                // 如果比拟的时候有多个子节点,须要更新oldFiber        if (oldFiber) {            oldFiber = oldFiber.sibling;        }        if (index === 0) { // 如果是第一个子节点,则作为以后fiber的子fiber            fiber.child = newFiber;        } else {            prevSibling.sibling = newFiber; // 将第二个以及之后的子节点作为上一个兄弟节点的兄弟节点        }        prevSibling = newFiber; // 将以后创立的fiber保留为其兄弟节点的上一个节点        index++;    }}function useState(init) {    // 从上一次渲染实现的函数组件对应的fiber的hooks数组中依据索引获取到对应的hook    const oldHook = functionComponentFiber.base && functionComponentFiber.base.hooks && functionComponentFiber.base.hooks[hookIndex];    const hook = { // 创立一个新的hook,state从上次中获取        state: oldHook? oldHook.state: init,        queue: []    };    const newStates = oldHook ? oldHook.queue : []; // 从上次hook中获取最新的状态    newStates.forEach(newState => {        hook.state = newState; // 更新hook    });    const setState = (newState) => {        hook.queue.push(newState); // 将新的状态放到hook的queue数组中        rootFiber = {            dom: oldRootFiber.dom,            props: oldRootFiber.props,            base: oldRootFiber        }        currentFiber = rootFiber;    }    functionComponentFiber.hooks.push(hook); // 将以后hook保留到函数组件对应的fiber节点的hooks数组中    hookIndex++; // 可能会有多个状态    return [hook.state, setState];}export default {    createElement,    render,    useState}

利用代码如下:

import React from "./react/index";let ReactDOM = React;function App(props) {    const [minAge, setMinAge] = React.useState(1);    const [maxAge, setMaxAge] = React.useState(100);    return (        <div>            <h1>minAge: {minAge}</h1>            <button onClick={() => setMinAge(minAge + 1)}>加</button>            <h1>maxAge: {maxAge}</h1>            <button onClick={() => setMaxAge(maxAge - 1)}>减</button>        </div>    );}ReactDOM.render(<App/>, document.getElementById("root"));