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

2021-01-22

人人都能读懂的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")
最新:
人人都能读懂的react源码解析(大厂高薪必备).1
人人都能读懂的react源码解析(大厂高薪必备).2
人人都能读懂的react源码解析(大厂高薪必备).3
人人都能读懂的react源码解析(大厂高薪必备).4
人人都能读懂的react源码解析(大厂高薪必备).5

潇晨简介:
前阿里、道富前端工程师
做一个程序员高效学习社区
请尊重劳动果实 否则追究法律责任

公众号
管理员wx