zoukankan      html  css  js  c++  java
  • Fiber学习

    生成virtual DOM

    bable转换jsx的结果

    let style = { border: "1px solid orange", margin: "5px" }
    let element = (
      <div id="A1" style={style}> A1
        <div id="B1" style={style}> B1
          <div id="C1" style={style}>C1</div>
          <div id="C2" style={style}>C2</div>
        </div>
        <div id="B2" style={style}>B2</div>
      </div>
    )
    //babel解析结果:
    // React.createElement("div", { id: "A1", style: style },
    //   " A1",
    //   React.createElement("div", { id: "B1", style: style },
    //     " B1",
    //     React.createElement("div", { id: "C1", style: style }, "C1"),
    //     React.createElement("div", { id: "C2", style: style }, "C2")
    //    ),
    //   React.createElement("div", { id: "B2", style: style }, "B2")
    // );
    

    实现createElment

    import { ELEMENT_TEXT } from './constants'
    function createElement(type, props, ...children) {
        delete props.__source;
        delete props.__self;
        return {
            type,
            props: {
                ...props,
                children: children.map(child => {
                    /* 文本节点特殊处理 */
                    return typeof child === 'object' ? child : {
                        type: ELEMENT_TEXT,
                        props: { text: child, children: [] }
                    }
                })
            }
    
        }
    }
    

    React.createElement处理过后的virtual DOM

    实现render方法

    render使用

    ReactDOM.render(
      element,
      document.getElementById('root')
    );
    

    react-dom入口文件

    import { TAG_ROOT } from "../react/constants"
    import scheduleRoot from './schedule'
    function render(element, container) {
        /* 根fiber */
        let rootFiber = {
            tag: TAG_ROOT,
            stateNode: container,
            props: { children: [element] }
        };
        scheduleRoot(rootFiber);
    }
    export default { render}
    

    1. 初始化

    let wrokProgressRoot = null;//记录根节点
    let nextUnitOfWork = null;//记录当前工作单元
    function scheduleRoot(rootFiber) {
        wrokProgressRoot = rootFiber;
        nextUnitOfWork = rootFiber;
    }
    

    2. 工作循环

    requestIdleCallback作用

    实现在浏览器空闲时运行workLoop,若超过500ms之不管是否空闲都运行(60帧显示器每16.6ms刷新一次,存在空闲时间则执行performUnitOfwork,一直到达500ms无空闲时间则强制执行)

    // 工作循环
    function workLoop(deadline) {
        // 时间片未到
        if ((deadline.timeout || deadline.timeRemaining() > 0) && nextUnitOfWork) {
            /* 执行工作单元,返回下一单元 */
            nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
        }
        /* 还有工作单元未执行 */
        if (!nextUnitOfWork && wrokProgressRoot) {
            console.log("Render 完成");
            // 生成DOM
            commitRoot();
        }
        requestIdleCallback(workLoop, { timeout: 500 });
    }
    requestIdleCallback(workLoop, { timeout: 500 });
    

    2.1 performUnitOfWork执行工作单元

    function performUnitOfWork(currentFiber) {
        //构建
        beginWork(currentFiber);
        /* 子元素 */
        if (currentFiber.child) {
            return currentFiber.child;
        }
        /* 没有子元素释放自己并往上找 */
        while (currentFiber) {
            /* fiber子元素全部完成,将自身合并到副作用链 */
            completeUnitOfWork(currentFiber);
            if (currentFiber.sibling) {
                return currentFiber.sibling;
            }
            /* 往上找 */
            currentFiber = currentFiber.return;
        }
    }
    

    2.1.1 beginWork分类构建

    function beginWork(currentFiber) {
        //根元素
        if (currentFiber.tag === TAG_ROOT) {
            upDateRoot(currentFiber);
        }
        //原生节点
        if (currentFiber.tag === TAG_HOST) {
            upDateHost(currentFiber);
        }
        //文本节点
        if (currentFiber.tag === TAG_TEXT) {
            upDateText(currentFiber);
        }
    }
    /* 更新根元素 */
    function upDateRoot(currentFiber) {
        /* 构建子元素 */
        let newChildren = currentFiber.props.children;
        reconcileChildren(currentFiber, newChildren);
    }
    /* 更新原生元素 */
    function upDateHost(currentFiber) {
        /* 创建DOM */
        if (!currentFiber.stateNode) {
            currentFiber.stateNode = createDOM(currentFiber);
        }
        /* 获取并构建子元素 */
        let newChildren = currentFiber.props.children;
        reconcileChildren(currentFiber, newChildren);
    }
    /* 更新文本元素 */
    function upDateText(currentFiber) {
        if (!currentFiber.stateNode) {
            currentFiber.stateNode = createDOM(currentFiber);
        }
    }
    

    createDOM方法创建Dom,设置属性

    /* 创建DOM */
    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);//<div></div>
            setProps(stateNode, {}, currentFiber.props);
            //<div id="A1" style="border: 1px solid orange; margin: 5px;"></div>
            return stateNode;
        }
    }
    

    reconcileChildren遍历子节点并记录父子兄弟fiber关系

    /* 创建子元素fiber并连接到父元素上 */
    function reconcileChildren(returnFiber, newChildren) {
        let newChildIndex = 0,prevSibling = null;
        //遍历returnFiber的子节点
        while (newChildIndex < newChildren.length) {
            let newChild = newChildren[newChildIndex];
            /* 标识文本和原生组件 */
            let tag;
            if (newChild.type === ELEMENT_TEXT) {
                tag = TAG_TEXT;
            } else if (typeof newChild.type === "string") {
                tag = TAG_HOST;
            }
            /* 创建Fiber */
            let newFiber = {
                tag,
                type: newChild.type,
                props: newChild.props,
                stateNode: null,
                return: returnFiber,//父节点
                effectTag: PLACEMENT,//操作
                nextEffect: null//下一个节点
            }
            /* 连接Fiber,第一个子元素作为儿子,其他作为兄弟连接 */
            if (newChildIndex === 0) {
                returnFiber.child = newFiber;
            } else {
                prevSibling.sibling = newFiber;
            }
            prevSibling = newFiber;
            newChildIndex++;
        }
    }
    

    2.1.2 completeUnitOfWork构建副作用链

    function completeUnitOfWork(currentFiber) {
        /* 获取父节点 */
        let returnFiber = currentFiber.return;
        if (returnFiber) {
            /* 将自己连接到父元素 */
            if (!returnFiber.firstEffect) {
                returnFiber.firstEffect = currentFiber.firstEffect;
            }
            if (currentFiber.lastEffect) {
                /* 将当前fiber头部接到父fiber尾部 */
                if (returnFiber.lastEffect) {
                    returnFiber.lastEffect.nextEffect = currentFiber.firstEffect;
                }
                /* 当前尾部作为最终尾部 */
                returnFiber.lastEffect = currentFiber.lastEffect;
            }
            /* 连接子元素 */
            if (currentFiber.effectTag === PLACEMENT) {
                /* 当前元素尾部有元素才会连接 */
                if (returnFiber.lastEffect) {
                    returnFiber.lastEffect.nextEffect = currentFiber;
                } else {
                    returnFiber.firstEffect = currentFiber;
                }
                /* 更新尾巴 */
                returnFiber.lastEffect = currentFiber;
            }
        }
    }
    

    2.2 commit挂载DOM

    function commitWork(currentFiber) {
        /* 获取父元素 */
        let returnDom = currentFiber.return.stateNode;
        /* 副作用类型 */
        if (currentFiber.effectTag === PLACEMENT) {
            returnDom.appendChild(currentFiber.stateNode);
        }
        /* 去除副作用 */
        currentFiber.effectTag = null;
    }
    function commitRoot() {
        /* 获取链表头 */
        let currentFiber = wrokProgressRoot.firstEffect;
        while (currentFiber) {
            commitWork(currentFiber);
            currentFiber = currentFiber.nextEffect;
        }
        wrokProgressRoot = null;
    }
    

    总结

    ReactDOM的render方法首先将虚拟DOM树进行扩充,记录节点的孩子和兄弟,然后将副作用节点按照自底向上的顺序记录在一个链表中,commit时实现从里到外改变DOM。

  • 相关阅读:
    安装gmsll
    常用LInux命令和操作
    yum 一键安装 jdk
    Linux目录详解,软件应该安装到哪个目录
    安装npm
    linux安装mysql (rpm + yum)
    springboot 打包jar 运行找资源文件
    rpm包安装java jar开机自启
    centos7设置服务开机自启
    linux(centos7) nginx 配置
  • 原文地址:https://www.cnblogs.com/aeipyuan/p/12990168.html
Copyright © 2011-2022 走看看