构建你自己的 React (16.8)

什么是fiber

Fiber通过链表的形式来记录节点之间的关系,可以被理解为划分一个个更小的执行单元,它是把一个大任务拆分为了很多个小块任务,一个小块任务的执行必须是一次完成的,不能出现暂停,但是一个小块任务执行完后可以移交控制权给浏览器去响应用户,从而不用像之前一样要等那个大任务一直执行完成再去响应用户。

代码执行流程
  1. 项目运行创建 “浏览器空闲加载函数” 用于实时监听 执行单元 (fiber)
  2. 获取页面dom容器(root),将jsx代码生成新的fiber
  3. 检测存在新的fiber,执行当前fiber,并返回下一个fiber, 在该fiber上获取旧的fiber,进行节点判断,分更新、新增、删除,记录删除的节点
  4. 对标识进行对应的处理

    /*重写 createElement */
    function createElement(type, props, ...children) {
        return {
            type,
            props: {
                ...props,
                children: children.map(child =>
                    typeof child === "object" ? child : createTextElement(child))
            }
        }
    }

    function createTextElement(text) {
        return {
            type: "TEXT_ELEMENT",
            props: {
                nodeValue: text,
                children: []
            }
        }
    }


    /*渲染与提交*/

    //事件监听
    const isEvent = key => key.startsWith("on")
    // 是否有子集
    const isProperty = key => key !== "children" && !isEvent(key)
    // 验证 属性值
    const isNew = (prev, next) => key => prev[key] !== next[key]
    //是否存在
    const isGone = (prev, next) => key => !(key in next)

    /**
     * 
     * @param dom 真实dom
     * @param prevProps 旧的
     * @param nextProps 新的
     */
    function updateDom(dom, prevProps, nextProps) {
        console.log("dom", dom, prevProps, nextProps)
        //删除旧的或更改的事件侦听器
        Object.keys(prevProps)
            .filter(isEvent)
            .filter(
                key => !(key in nextProps) || isNew(prevProps, nextProps)(key)
            )
            .forEach(name => {
                const eventType = name.toLowerCase().substring(2)
                dom.removeEventListener(eventType, prevProps[name])
            })
        //删除旧属性
        Object.keys(prevProps)
            .filter(isProperty)//获取所以有子集的节点
            .filter(isGone(prevProps, nextProps))//获取属性值一致的的节点
            .forEach(name => {
                dom[name] = ""
            })
        //设置新属性或更改的属性
        Object.keys(nextProps)
            .filter(isProperty)
            .filter(isNew(prevProps, nextProps))
            .forEach(name => {1
                dom[name] = nextProps[name]
            })
        //添加事件侦听器
        Object.keys(nextProps)
            .filter(isEvent)
            .filter(isNew(prevProps, nextProps))
            .forEach(name => {
                const eventType = name.toLowerCase().substring(2)
                dom.addEventListener(eventType, nextProps[name])
            })
    }
    // Fiber 树提交给 DOM
    function commitRoot() {
        deletions.forEach(commitWork)
        commitWork(wipRoot.child)
        currentRoot = wipRoot
        wipRoot = null
    }

    function commitWork(fiber) {
        console.log("fiber", fiber)
        if (!fiber) return
        //要找到 DOM 节点的父节点,我们需要沿着纤程树向上查找,直到找到具有 DOM 节点的纤程
        let domParentFiber = fiber.parent
        while (!domParentFiber.dom) {
            domParentFiber = domParentFiber.parent
        }
        const domParent = domParentFiber.dom
        //fiber递归转换为dom
        // const domParent = fiber.parent.dom
        if (
            fiber.effectTag === "PLACEMENT" &&
            fiber.dom != null
        ) {
            //将 PLACEMENT 标识的添加到父DOM上
            domParent.appendChild(fiber.dom)
        } else if (
            fiber.effectTag === "UPDATE" &&
            fiber.dom != null
        ) {
            //更改 UPDATE 标识的props
            updateDom(
                fiber.dom,
                fiber.alternate.props,
                fiber.props
            )
        } else if (fiber.effectTag === "DELETION") {
            // 相反!删除 DELETION 标识的节点
            commitDeletion(fiber, domParent)
        }
        commitWork(fiber.child)
        commitWork(fiber.sibling)
    }


    /*重写 render */
    function createDom(fiber) {
        //元素类型创建 DOM 节点。如果元素类型是TEXT_ELEMENT我们创建一个文本节点而不是常规节点
        // const dom = document.createElement(element.type)
        const dom = fiber.type === "TEXT_ELEMENT"
            ? document.createTextNode("")
            : document.createElement(fiber.type)

        //分配元素的属性
        // const isProperty = key => key !== "children"
        // Object.keys(element.props).filter(isProperty).forEach(name => {
        //     dom[name] = element.props[name]
        // })
        updateDom(dom, {}, fiber.props)
        return dom
    }
    //并且当移除一个节点时,我们还需要继续,直到我们找到一个带有 DOM 节点的子节点
    function commitDeletion(fiber, domParent) {
        if (fiber.dom) {
            domParent.removeChild(fiber.dom)
        } else {
            commitDeletion(fiber.child, domParent)
        }
    }

    function render(element, container) {
        /*当前进行中的 fiber */
        wipRoot = {
            dom: container,
            props: {
                children: [element]
            },
            alternate: currentRoot // alternate 旧的 fiber
        }
        //跟踪我们想要删除的节点 初始化
        deletions = []
        console.log("wipRoot", wipRoot)
        //该 fiber 也将是下一个执行单位(工作区)
        nextUnitOfWork = wipRoot
    }

    /*并发 时间切片*/
    /** 下一个工作区*/
    let nextUnitOfWork = null
    /** 进行中的根 */
    let wipRoot = null
    /** 保存提交给 DOM 的最后一个纤程树*/
    let currentRoot = null
    /** 跟踪我们想要删除的节点*/
    let deletions = null

    // 工作循环
    function workLoop(deadline) {
        let shouldYield = false

        //当前工作区不为空时
        while (nextUnitOfWork && !shouldYield) {
            //执行工作区 (fiber)
            nextUnitOfWork = performUnitOfWork(nextUnitOfWork)

            shouldYield = deadline.timeRemaining() < 1

            console.log("shouldYield",shouldYield)
        }
        //没有下一个工作单元,且存在根时
        if (!nextUnitOfWork && wipRoot) {
            //Fiber 树提交给 DOM
            commitRoot()
        }

        requestIdleCallback(workLoop)
    }


    //浏览器空闲时期被调用 执行最新工作区
    requestIdleCallback(workLoop)

    //执行工作单元
    function performUnitOfWork(fiber) {
        const isFunctionComponent = fiber.type instanceof Function
        //将新旧fiber的子集进行对比,划分出标识
        if (isFunctionComponent) {
            //fiber 是否为函数 hooks
            updateFunctionComponent(fiber)
        } else {
            //fiber 非函数类型时
            updateHostComponent(fiber)
        }
        //寻找下一个工作单元
        if (fiber.child) {//孩子
            console.log("fiber.child",fiber)
            return fiber.child
        }
        let nextFiber = fiber
        while (nextFiber) {
            if (nextFiber.sibling) {//兄弟
                return nextFiber.sibling
            }
            nextFiber = nextFiber.parent //叔叔
        }
    }

    let wipFiber = null
    let hookIndex = null
/** 将新旧fiber的子集进行对比,划分出标识*/
    function updateFunctionComponent(fiber) {
        wipFiber = fiber
        hookIndex = 0
        wipFiber.hooks = []
        const children = [fiber.type(fiber.props)]
        reconcileChildren(fiber, children)
    }

    function useState(initial) {
        const oldHook = wipFiber.alternate &&
            wipFiber.alternate.hooks &&
            wipFiber.alternate.hooks[hookIndex]
        const hook = {
            state: oldHook ? oldHook.state : initial,
            query: []
        }
        console.log("hooks", hook)
        const action = oldHook ? oldHook.query : []
        action.forEach(action => {
            hook.state = action(hook.state)
        })
        const setState = action => {
            hook.query.push(action)
            wipRoot = {
                dom: currentRoot.dom,
                props: currentRoot.props,
                alternate: currentRoot
            }
            //将一个新的正在进行的工作根设置为下一个工作单元,这样工作循环就可以开始一个新的渲染阶段
            nextUnitOfWork = wipRoot
            deletions = []
        }
        wipFiber.hooks.push(hook)
        hookIndex++
        return [hook.state, setState]
    }

    function updateHostComponent(fiber) {
        //当fiber为空是 创建一个新节点并将其附加到 DOM
        if (!fiber.dom) {
            fiber.dom = createDom(fiber)
        }
        //每次我们处理一个元素时,我们都会向 DOM 添加一个新节点  浏览器可能会在我们完成渲染整个树之前中断我们的工作。在这种情况下,用户将看到一个不完整的 UI。我们不希望这样。
        // if(fiber.parent){
        //     fiber.parent.dom.appendChild(fiber.dom)
        // }
        //然后我们为每个孩子创造一个新的纤维
        reconcileChildren(fiber, fiber.props.children)
    }

    //这element是我们想要渲染到 DOM 的东西,oldFiber也是我们上次渲染的东西
    function reconcileChildren(wipFiber, children) {
        let index = 0
        //上次渲染的 fiber 的子集
        let oldFiber = wipFiber.alternate && wipFiber.alternate.child
        let prevSibling = null
        while (index < children.length || oldFiber != null) {
            //获取当前 fiber 的子集
            const element = children[index]
            let newFiber = null

            const sameType = oldFiber && element && element.type == oldFiber.type

            //当旧的 Fiber 和元素的类型相同时,我们创建一个新的 Fiber,保留旧 Fiber 的 DOM 节点和元素的 props。
            if (sameType) {
                newFiber = {
                    type: oldFiber.type,
                    props: element.props,
                    dom: oldFiber.dom,
                    parent: wipFiber,//记录当前fiber的父级
                    alternate: oldFiber,
                    effectTag: "UPDATE",//fiber 标记 更新
                }
            }
            if (element && !sameType) {
                newFiber = {
                    type: element.type,
                    props: element.props,
                    dom: null,
                    parent: wipFiber,
                    alternate: null,
                    effectTag: "PLACEMENT",//fiber 标记 新的
                }
            }
            //删除节点
            if (oldFiber && !sameType) {
                oldFiber.effectTag = "DELETION"//fiber 标记 删除
                deletions.push(oldFiber)
            }
            if (oldFiber) {
                oldFiber = oldFiber.sibling
            }
            // const newFiber = {
            //     type: element.type,
            //     props: element.props,
            //     parent: fiber,
            //     dom: null
            // }
            if (index === 0) {
                wipFiber.child = newFiber
            } else {
                prevSibling.sibling = newFiber
            }
            prevSibling = newFiber
            index++
        }
    }

    /*end 并发 时间切片*/
    const Didact = {
        createElement,
        render,
        useState,
    }

    /** @jsx Didact.createElement */
    function Counter() {
        const [state, setState] = Didact.useState(1)
        return (
            <h1 onClick={() => setState(c => c + 1)}>
                Count: {state}
            </h1>
        )
    }

    // const element = Didact.createElement(
    //     "div",
    //     {id: "foo"},
    //     Didact.createElement("a", null, "bar"),
    //     Didact.createElement("b"),
    // )
    /*JSX转换*/
    const element = <Counter/>
    const container = document.getElementById("root")
    Didact.render(element, container

注:主要用过个人记录学习

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值