写点什么

12. 手写迷你 react(短小精悍就是我)

用户头像
全栈潇晨
关注
发布于: 2021 年 03 月 04 日

人人都能读懂的 react 源码解析(大厂高薪必备)

12.手写迷你 react(短小精悍就是我)

视频课程 &调试 demos

​ 视频课程的目的是为了快速掌握 react 源码运行的过程和 react 中的 scheduler、reconciler、renderer、fiber 等,并且详细 debug 源码和分析,过程更清晰。

​ 视频课程:进入课程

​ demos:demo

课程结构:

  1. 开篇(听说你还在艰难的啃react源码)

  2. react心智模型(来来来,让大脑有react思维吧)

  3. Fiber(我是在内存中的dom)

  4. 从legacy或concurrent开始(从入口开始,然后让我们奔向未来)

  5. state更新流程(setState里到底发生了什么)

  6. render阶段(厉害了,我有创建Fiber的技能)

  7. commit阶段(听说renderer帮我们打好标记了,映射真实节点吧)

  8. diff算法(妈妈再也不担心我的diff面试了)

  9. hooks源码(想知道Function Component是怎样保存状态的嘛)

  10. scheduler&lane模型(来看看任务是暂停、继续和插队的)

  11. concurrent mode(并发模式是什么样的)

  12. 手写迷你react(短小精悍就是我)

迷你 react 和真正的源码有哪些区别呢

  • 在 render 阶段我们遍历了整颗 Fiber 树,在源码中如果节点什么都没改变会命中优化的逻辑,然后跳过这个节点的遍历

  • commit 我们也遍历了整颗 Fiber 树,源码中只遍历带有 effect 的 Fiber 节点,也就是遍历 effectList

  • 每次遍历的时候我们都是新建节点,源码中某些条件会复用节点

  • 没有用到优先级

第一步:渲染器和入口函数

const React = {    createElement,    render,  }    const container = document.getElementById("root")    const updateValue = e => {    rerender(e.target.value)  }    const rerender = value => {    const element = (      <div>        <input onInput={updateValue} value={value} />        <h2>Hello {value}</h2>      </div>    )    React.render(element, container)  }    rerender("World")
复制代码

第二步:创建 dom 节点函数

function createElement(type, props, ...children) {//创建element    return {      type,      props: {        ...props,        children: children.map(child =>          typeof child === "object"            ? child            : createTextElement(child)        ),      },    }  }    function createTextElement(text) {//创建text类型    return {      type: "TEXT_ELEMENT",      props: {        nodeValue: text,        children: [],      },    }  }    function createDom(fiber) {//创建dom    const dom =      fiber.type == "TEXT_ELEMENT"        ? document.createTextNode("")        : document.createElement(fiber.type)      updateDom(dom, {}, fiber.props)      return dom  }
复制代码

第三步:更新节点函数

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)  function updateDom(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 => { 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] ) }) }
复制代码

第四步:render 阶段

function performUnitOfWork(fiber) {//render阶段    if (!fiber.dom) {      fiber.dom = createDom(fiber)    }      const elements = fiber.props.children    reconcileChildren(fiber, elements)      if (fiber.child) {      return fiber.child    }    let nextFiber = fiber    while (nextFiber) {      if (nextFiber.sibling) {        return nextFiber.sibling      }      nextFiber = nextFiber.parent    }  }    function reconcileChildren(wipFiber, elements) {//reconcile节点    let index = 0    let oldFiber =      wipFiber.alternate && wipFiber.alternate.child    let prevSibling = null      while (      index < elements.length ||      oldFiber != null    ) {      const element = elements[index]      let newFiber = null        const sameType =        oldFiber &&        element &&        element.type == oldFiber.type        if (sameType) {        newFiber = {          type: oldFiber.type,          props: element.props,          dom: oldFiber.dom,          parent: wipFiber,          alternate: oldFiber,          effectTag: "UPDATE",        }      }      if (element && !sameType) {        newFiber = {          type: element.type,          props: element.props,          dom: null,          parent: wipFiber,          alternate: null,          effectTag: "PLACEMENT",        }      }      if (oldFiber && !sameType) {        oldFiber.effectTag = "DELETION"        deletions.push(oldFiber)      }        if (oldFiber) {        oldFiber = oldFiber.sibling      }        if (index === 0) {        wipFiber.child = newFiber      } else if (element) {        prevSibling.sibling = newFiber      }        prevSibling = newFiber      index++    }  }
复制代码

第五步:commit 阶段

function commitRoot() {//commit阶段    deletions.forEach(commitWork)    commitWork(wipRoot.child)    currentRoot = wipRoot    wipRoot = null  }    function commitWork(fiber) {//操作真实dom    if (!fiber) {      return    }      const domParent = fiber.parent.dom    if (      fiber.effectTag === "PLACEMENT" &&      fiber.dom != null    ) {      domParent.appendChild(fiber.dom)    } else if (      fiber.effectTag === "UPDATE" &&      fiber.dom != null    ) {      updateDom(        fiber.dom,        fiber.alternate.props,        fiber.props      )    } else if (fiber.effectTag === "DELETION") {      domParent.removeChild(fiber.dom)    }      commitWork(fiber.child)    commitWork(fiber.sibling)  }
复制代码

第六步:开始调度

function render(element, container) {//渲染开始的入口    wipRoot = {      dom: container,      props: {        children: [element],      },      alternate: currentRoot,    }    deletions = []    nextUnitOfWork = wipRoot  }    let nextUnitOfWork = null  let currentRoot = null  let wipRoot = null  let deletions = null    function workLoop(deadline) {//调度函数    let shouldYield = false    while (nextUnitOfWork && !shouldYield) {      nextUnitOfWork = performUnitOfWork(//render阶段        nextUnitOfWork      )      shouldYield = deadline.timeRemaining() < 1    }      if (!nextUnitOfWork && wipRoot) {      commitRoot()//commit阶段    }      requestIdleCallback(workLoop)//空闲调度  }    requestIdleCallback(workLoop)
复制代码

完整代码

function createElement(type, props, ...children) {//创建element    return {      type,      props: {        ...props,        children: children.map(child =>          typeof child === "object"            ? child            : createTextElement(child)        ),      },    }  }    function createTextElement(text) {//创建text类型    return {      type: "TEXT_ELEMENT",      props: {        nodeValue: text,        children: [],      },    }  }    function createDom(fiber) {//创建dom    const dom =      fiber.type == "TEXT_ELEMENT"        ? document.createTextNode("")        : document.createElement(fiber.type)      updateDom(dom, {}, fiber.props)      return dom  }    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)  function updateDom(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 => { 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] ) }) } function commitRoot() {//commit阶段 deletions.forEach(commitWork) commitWork(wipRoot.child) currentRoot = wipRoot wipRoot = null } function commitWork(fiber) {//操作真实dom if (!fiber) { return } const domParent = fiber.parent.dom if ( fiber.effectTag === "PLACEMENT" && fiber.dom != null ) { domParent.appendChild(fiber.dom) } else if ( fiber.effectTag === "UPDATE" && fiber.dom != null ) { updateDom( fiber.dom, fiber.alternate.props, fiber.props ) } else if (fiber.effectTag === "DELETION") { domParent.removeChild(fiber.dom) } commitWork(fiber.child) commitWork(fiber.sibling) } function render(element, container) {//渲染开始的入口 wipRoot = { dom: container, props: { children: [element], }, alternate: currentRoot, } deletions = [] nextUnitOfWork = wipRoot } let nextUnitOfWork = null let currentRoot = null let wipRoot = null let deletions = null function workLoop(deadline) {//调度函数 let shouldYield = false while (nextUnitOfWork && !shouldYield) { nextUnitOfWork = performUnitOfWork(//render阶段 nextUnitOfWork ) shouldYield = deadline.timeRemaining() < 1 } if (!nextUnitOfWork && wipRoot) { commitRoot()//commit阶段 } requestIdleCallback(workLoop)//空闲调度 } requestIdleCallback(workLoop) function performUnitOfWork(fiber) {//render阶段 if (!fiber.dom) { fiber.dom = createDom(fiber) } const elements = fiber.props.children reconcileChildren(fiber, elements) if (fiber.child) { return fiber.child } let nextFiber = fiber while (nextFiber) { if (nextFiber.sibling) { return nextFiber.sibling } nextFiber = nextFiber.parent } } function reconcileChildren(wipFiber, elements) {//reconcile节点 let index = 0 let oldFiber = wipFiber.alternate && wipFiber.alternate.child let prevSibling = null while ( index < elements.length || oldFiber != null ) { const element = elements[index] let newFiber = null const sameType = oldFiber && element && element.type == oldFiber.type if (sameType) { newFiber = { type: oldFiber.type, props: element.props, dom: oldFiber.dom, parent: wipFiber, alternate: oldFiber, effectTag: "UPDATE", } } if (element && !sameType) { newFiber = { type: element.type, props: element.props, dom: null, parent: wipFiber, alternate: null, effectTag: "PLACEMENT", } } if (oldFiber && !sameType) { oldFiber.effectTag = "DELETION" deletions.push(oldFiber) } if (oldFiber) { oldFiber = oldFiber.sibling } if (index === 0) { wipFiber.child = newFiber } else if (element) { prevSibling.sibling = newFiber } prevSibling = newFiber index++ } } const React = { createElement, render, } const container = document.getElementById("root") const updateValue = e => { rerender(e.target.value) } const rerender = value => { const element = ( <div> <input onInput={updateValue} value={value} /> <h2>Hello {value}</h2> </div> ) React.render(element, container) } rerender("World")
复制代码


用户头像

全栈潇晨

关注

还未添加个人签名 2021.02.17 加入

还未添加个人简介

评论

发布
暂无评论
12.手写迷你react(短小精悍就是我)