前言

这篇文章循序渐进地介绍实现以下几个概念,遵循本篇文章根本就能搞懂为啥须要fiber,为啥须要commit和phases、reconciliation阶段等原理。本篇文章又不齐全和原文统一,这里会退出我本人的一些思考,比方通过performUnitOfWork解决后fiber tree和element tree的分割等。

  • createElement函数
  • render函数
  • Concurrent Mode
  • Fibers
  • Render and Commit Phases
  • Reconciliation
  • Function Components
  • Hooks

第一章 基本概念

以上面代码为例

// 1.jsx语法不是非法的js语法// const element = <h1 title="foo">Hello</h1>// 2.经babel等编译工具将jsx转换成js,将jsx转换成createElement函数调用的形式// const element = React.createElement(//   "h1",//   { title: "foo" },//   "Hello"// )// 3.React.createElement返回的最终对象大抵如下:const element = {  type: "h1",  props: {    title: "foo",    children: "Hello",  },}const container = document.getElementById("root")// ReactDOM.render(element, container)// 4.替换ReactDOM.render函数的逻辑,ReactDOM.render大抵解决逻辑:const node = document.createElement(element.type)node['title'] = element.props.titleconst text = document.createTextNode("")text["nodeValue"] = element.props.childrennode.appendChild(text)container.appendChild(node)

为了防止歧义,这里应用 element 示意 React elementsnode 示意实在的DOM元素节点。

至此,这段代码无需通过任何编译曾经可能在浏览器上跑起来了,不信你能够复制到浏览器控制台试试

这里有几点须要留神:

  • 先通过node.appendChild(text)将子元素增加到父元素,而后再通过container.appendChild(node)将父元素增加到容器container中触发浏览器渲染页面。这个程序不能反过来,也就是说只有整个实在dom树构建实现能力增加到容器中。假如这个程序反过来,比方先执行container.appendChild(node),则触发浏览器回流。再执行node.appendChild(text)又触发浏览器回流。性能极差
  • React.createElement返回的最终的对象就是virtual dom树,ReactDOM.render依据这个virtual dom创立实在的dom树

第二章 createElement 函数

以上面的代码为例

React.createElement接管的children有可能是原子值,比方字符串或者数字等,React.createElement('h1', {title: 'foo'}, 'Hello')。为了简化咱们的代码,创立一个非凡的TEXT_ELEMENT 类型将其转换成对象

React.createElement = (type, props, ...children) => {  return {    type,    props: {      ...props,      children: children.map(child => {        if(typeof child === 'object'){          return child        }        return {          type: 'TEXT_ELEMENT',          props: {            nodeValue: child,            children: [],          }        }      })    }  }}// const element = (//   <div id="foo">//     <a>bar</a>//     <b />//   </div>// )// 将jsx转换成js语法const element = React.createElement(  "div",  { id: "foo" },  React.createElement("a", null, "bar"),  React.createElement("b"))const container = document.getElementById("root")ReactDOM.render(element, container)

好了,当初咱们曾经实现了一个简略的createElement函数,咱们能够通过一段非凡的正文来通知babel在将jsx转换成js时应用咱们本人的createElement函数:

const MiniReact = {  createElement:  (type, props, ...children) => {    return {      type,      props: {        ...props,        children: children.map(child => {          if(typeof child === 'object'){            return child          }          return {            type: 'TEXT_ELEMENT',            props: {              nodeValue: child,              children: [],            }          }        })      }    }  }}/** @jsx MiniReact.createElement */const element = (  <div id="foo">    <a>bar</a>    <b />  </div>)console.log('element======', element)const container = document.getElementById("root")ReactDOM.render(element, container)

第三章 render函数

import React from 'react';function render(element, container) {  const dom = element.type === 'TEXT_ELEMENT' ? document.createTextNode("") : document.createElement(element.type)  const isProperty = key => key !== 'children'  Object.keys(element.props)    .filter(isProperty)    .forEach(name => {      dom[name] = element.props[name]    })  element.props.children.forEach(child => {    render(child, dom)  });  container.appendChild(dom)}const MiniReact = {  createElement:  (type, props, ...children) => {    return {      type,      props: {        ...props,        children: children.map(child => {          if(typeof child === 'object'){            return child          }          return {            type: 'TEXT_ELEMENT',            props: {              nodeValue: child,              children: [],            }          }        })      }    }  },  render}/** @jsx MiniReact.createElement */const element = (  <div id="foo">    <a>bar</a>    <b />  </div>)console.log('element======', element)const container = document.getElementById("root")MiniReact.render(element, container)

render函数递归创立实在的dom元素,而后将各个元素append到其父元素中,最初整个dom树append到root container中,渲染实现,这个过程一旦开始,两头是无奈打断的,直到整个利用渲染实现。这也是React16版本以前的渲染过程

留神,只有当整个dom树append到root container中时,页面才会显示

第四章 Concurrent Mode

在第三章中能够看到,以后版本的render函数是递归构建dom树,最初才append到root container,最终页面才渲染进去。这里有个问题,如果dom节点数量宏大,递归层级过深,这个过程其实是很耗时的,导致render函数长时间占用主线程,浏览器无奈响应用户输出等事件,造成卡顿的景象。参考React实战视频解说:进入学习

因而咱们须要将render过程拆分成小的工作单元,每执行完一个单元,都容许浏览器打断render过程并执行高优先级的工作,等浏览器无暇再继续执行render过程

如果对requestIdleCallback不相熟的,能够自行理解一下。实在React代码中并没有应用这个api,因为有兼容性问题。因而React应用scheduler package模仿这个调度过程

let nextUnitOfWork = nullfunction workLoop(deadline) {  let shouldYield = false  while (nextUnitOfWork && !shouldYield) {    nextUnitOfWork = performUnitOfWork(      nextUnitOfWork    )    shouldYield = deadline.timeRemaining() < 1  }  requestIdleCallback(workLoop)}requestIdleCallback(workLoop)function performUnitOfWork(nextUnitOfWork) {  // TODO}

performUnitOfWork接管当前工作单元,并返回下一个工作单元。工作单元能够了解为就是一个fiber对象节点

workLoop循环里会循环调用performUnitOfWork,直到所有工作单元都曾经处理完毕,或者以后帧浏览器曾经没有闲暇工夫,则循环终止。等下次浏览器闲暇工夫再接着继续执行

因而咱们须要一种数据结构,可能反对工作打断并且能够接着继续执行,很显然,链表就非常适合

第五章 Fibers

Fibers就是一种数据结构,反对将渲染过程拆分成工作单元,实质上就是一个双向链表。这种数据结构的益处就是不便找到下一个工作单元

Fiber蕴含三层含意:

  • 作为架构来说,之前React 15Reconciler采纳递归的形式执行,数据保留在递归调用栈中,所以被称为stack ReconcilerReact 16Reconciler基于Fiber节点实现,被称为Fiber Reconciler
  • 作为动态的数据结构来说,每个Fiber节点对应一个React Element,保留了该组件的类型(函数组件/类组件/html标签)、对应的DOM节点信息等
  • 作为动静的工作单元来说,每个Fiber节点保留了本次更新中该组件扭转的状态、要执行的工作等

Fiber的几点冷常识:

  • 一个Fiber节点对应一个React Element节点,同时也是一个工作单元
  • 每个fiber节点都有指向第一个子元素,下一个兄弟元素,父元素的指针**

以上面代码为例:

MiniReact.render(  <div>    <h1>      <p />      <a />    </h1>    <h2 />  </div>,  container)

对应的fiber tree如下:

import React from 'react';// 依据fiber节点创立实在的dom节点function createDom(fiber) {  const dom = fiber.type === 'TEXT_ELEMENT' ? document.createTextNode("") : document.createElement(fiber.type)  const isProperty = key => key !== 'children'  Object.keys(fiber.props)    .filter(isProperty)    .forEach(name => {      dom[name] = fiber.props[name]    })  return dom}let nextUnitOfWork = null// render函数次要逻辑://   依据root container容器创立root fiber//   将nextUnitOfWork指针指向root fiber//   element是react element treefunction render(element, container){  nextUnitOfWork = {    dom: container,    props: {      children: [element], // 此时的element还只是React.createElement函数创立的virtual dom树    },  }}function workLoop(deadline) {  let shouldYield = false  while (nextUnitOfWork && !shouldYield) {    nextUnitOfWork = performUnitOfWork(nextUnitOfWork)    shouldYield = deadline.timeRemaining() < 1  }  requestIdleCallback(workLoop)}requestIdleCallback(workLoop)// performUnitOfWork函数次要逻辑://   将element元素增加到DOM//   给element的子元素创立对应的fiber节点//   返回下一个工作单元,即下一个fiber节点,查找过程://      1.如果有子元素,则返回子元素的fiber节点//      2.如果没有子元素,则返回兄弟元素的fiber节点//      3.如果既没有子元素又没有兄弟元素,则往上查找其父节点的兄弟元素的fiber节点//      4.如果往上查找到root fiber节点,阐明render过程曾经完结function performUnitOfWork(fiber) {  // 第一步 依据fiber节点创立实在的dom节点,并保留在fiber.dom属性中  if(!fiber.dom){    fiber.dom = createDom(fiber)  }  // 第二步 将以后fiber节点的实在dom增加到父节点中,留神,这一步是会触发浏览器回流重绘的!!!  if(fiber.parent){    fiber.parent.dom.appendChild(fiber.dom)  }  // 第三步 给子元素创立对应的fiber节点  const children = fiber.props.children  let prevSibling  children.forEach((child, index) => {    const newFiber = {      type: child.type,      props: child.props,      parent: fiber,      dom: null    }    if(index === 0){      fiber.child = newFiber    } else {      prevSibling.sibling = newFiber    }    prevSibling = newFiber  })  // 第四步,查找下一个工作单元  if(fiber.child){    return fiber.child  }  let nextFiber = fiber  while(nextFiber){    if(nextFiber.sibling){      return nextFiber.sibling    }    nextFiber = nextFiber.parent  }}const MiniReact = {  createElement:  (type, props, ...children) => {    return {      type,      props: {        ...props,        children: children.map(child => {          if(typeof child === 'object'){            return child          }          return {            type: 'TEXT_ELEMENT',            props: {              nodeValue: child,              children: [],            }          }        })      }    }  },  render}/** @jsx MiniReact.createElement */const element = (  <div>    <h1>      <p />      <a />    </h1>    <h2 />  </div>)// const element = (//   <div id="foo">//     <a>bar</a>//     <b />//   </div>// )console.log('element======', element)const container = document.getElementById("root")MiniReact.render(element, container)

这里有一点值得细品,React.createElement返回的element treeperformUnitOfWork创立的fiber tree有什么分割。如下图所示:

  • React Element Tree是由React.createElement办法创立的树形构造对象
  • Fiber Tree是依据React Element Tree创立来的树。每个Fiber节点保留着实在的DOM节点,并且保留着对React Element Tree中对应的Element节点的利用。留神,Element节点并不会保留对Fiber节点的利用

第六章 Render and Commit Phases

第五章的performUnitOfWork有些问题,在第二步中咱们间接将新创建的实在dom节点挂载到了容器上,这样会带来两个问题:

  • 每次执行performUnitOfWork都会造成浏览器回流重绘,因为实在的dom曾经被增加到浏览器上了,性能极差
  • 浏览器是能够打断渲染过程的,因而可能会造成用户看到不残缺的UI界面

咱们须要革新一下咱们的代码,在performUnitOfWork阶段不把实在的dom节点挂载到容器上。保留fiber tree根节点的援用。等到fiber tree构建实现,再一次性提交实在的dom节点并且增加到容器上。

import React from 'react';function createDom(fiber) {  const dom = fiber.type === 'TEXT_ELEMENT' ? document.createTextNode("") : document.createElement(fiber.type)  const isProperty = key => key !== 'children'  Object.keys(fiber.props)    .filter(isProperty)    .forEach(name => {      dom[name] = fiber.props[name]    })  return dom}let nextUnitOfWork = nulllet wipRoot = nullfunction render(element, container){  wipRoot = {    dom: container,    props: {      children: [element], // 此时的element还只是React.createElement函数创立的virtual dom树    },  }  nextUnitOfWork = wipRoot}function commitRoot(){  commitWork(wipRoot.child)  wipRoot = null}function commitWork(fiber){  if(!fiber){    return  }  const domParent = fiber.parent.dom;  domParent.appendChild(fiber.dom)  commitWork(fiber.child)  commitWork(fiber.sibling)}function workLoop(deadline) {  let shouldYield = false  while (nextUnitOfWork && !shouldYield) {    nextUnitOfWork = performUnitOfWork(nextUnitOfWork)    shouldYield = deadline.timeRemaining() < 1  }  if(!nextUnitOfWork && wipRoot){    commitRoot()  }  requestIdleCallback(workLoop)}requestIdleCallback(workLoop)function performUnitOfWork(fiber) {  // 第一步 依据fiber节点创立实在的dom节点,并保留在fiber.dom属性中  if(!fiber.dom){    fiber.dom = createDom(fiber)  }  // 第二步 将以后fiber节点的实在dom增加到父节点中,留神,这一步是会触发浏览器回流重绘的!!!  // if(fiber.parent){  //   fiber.parent.dom.appendChild(fiber.dom)  // }  // 第三步 给子元素创立对应的fiber节点  const children = fiber.props.children  let prevSibling  children.forEach((child, index) => {    const newFiber = {      type: child.type,      props: child.props,      parent: fiber,      dom: null    }    if(index === 0){      fiber.child = newFiber    } else {      prevSibling.sibling = newFiber    }    prevSibling = newFiber  })  // 第四步,查找下一个工作单元  if(fiber.child){    return fiber.child  }  let nextFiber = fiber  while(nextFiber){    if(nextFiber.sibling){      return nextFiber.sibling    }    nextFiber = nextFiber.parent  }}const MiniReact = {  createElement:  (type, props, ...children) => {    return {      type,      props: {        ...props,        children: children.map(child => {          if(typeof child === 'object'){            return child          }          return {            type: 'TEXT_ELEMENT',            props: {              nodeValue: child,              children: [],            }          }        })      }    }  },  render}/** @jsx MiniReact.createElement */const element = (  <div>    <h1>      <p />      <a />    </h1>    <h2 />  </div>)// const element = (//   <div id="foo">//     <a>bar</a>//     <b />//   </div>// )console.log('element======', element)const container = document.getElementById("root")MiniReact.render(element, container)

第七章 Reconciliation

目前为止,咱们只思考增加dom节点到容器上这一繁多场景,更新删除还没实现。

咱们须要比照最新的React Element Tree和最近一次的Fiber Tree的差别

咱们须要给每个fiber节点增加一个alternate属性来保留旧的fiber节点

alternate保留的旧的fiber节点次要有以下几个用处:

  • 复用旧fiber节点上的实在dom节点
  • 旧fiber节点上的props和新的element节点的props比照
  • 旧fiber节点上保留有更新的队列,在创立新的fiber节点时执行这些队列以获取最新的页面
  const children = fiber.props.children  reconcileChildren(fiber, children)  function reconcileChildren(wipFiber, elements) {    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++    }}

如上代码所示:

协调过程:

  • 实质上仍然是依据新的React Element Tree创立新的Fiber Tree,不过为了节俭内存开销,协调过程会判断新的fiber节点是否复用旧的fiber节点上的实在dom元素,如果能复用,就不须要再从头到尾全副从新创立一遍实在的dom元素。同时每个新fiber节点上还会保留着对旧fiber节点的援用,不便在commit阶段做新旧属性props的比照。
  • 如果old fiber.typenew element.type雷同,则保留旧的dom节点,只更新props属性
  • 如果type不雷同并且有new element,则创立一个新的实在dom节点
  • 如果type不同并且有old fiber节点,则删除该节点对应的实在dom节点
  • 删除节点须要有个专门的数组收集须要删除的旧的fiber节点。因为新的element tree创立进去的新的fiber tree不存在对应的dom,因而须要收集旧的fiber节点,并在commit阶段删除

留神,协调过程,还是以最新的React Element Tree为主去创立一个新的fiber tree,只不过是新的fiber节点复用旧的fiber节点的实在dom元素,毕竟频繁创立实在dom是很耗费内存的。新的fiber节点还是会保留着对旧的fiber节点的援用,不便在commit阶段进行新属性和旧属性的比拟。这里会有个问题,如果新fiber节点保留旧fiber节点的援用,那么随着更新次数越来越多,旧的fiber tree是不是也会越来越多,如何销毁?

import React from 'react';function createDom(fiber) {  const dom = fiber.type === 'TEXT_ELEMENT' ? document.createTextNode("") : document.createElement(fiber.type)  updateDom(dom, {}, fiber.props)  return dom}let nextUnitOfWork = nulllet wipRoot = null // 保留着对root fiber的援用let currentRoot = null // 保留着当前页面对应的fiber treelet deletions = nullfunction render(element, container){  wipRoot = {    dom: container,    props: {      children: [element], // 此时的element还只是React.createElement函数创立的virtual dom树    },    alternate: currentRoot,  }  deletions = []  nextUnitOfWork = wipRoot}function commitRoot(){  deletions.forEach(commitWork)  commitWork(wipRoot.child)  currentRoot = wipRoot  wipRoot = null}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) {  //Remove old or changed event listeners  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]      )    })  // Remove old properties  Object.keys(prevProps)    .filter(isProperty)    .filter(isGone(prevProps, nextProps))    .forEach(name => {      dom[name] = ""    })  // Set new or changed properties  Object.keys(nextProps)    .filter(isProperty)    .filter(isNew(prevProps, nextProps))    .forEach(name => {      dom[name] = nextProps[name]    })  // Add event listeners  Object.keys(nextProps)    .filter(isEvent)    .filter(isNew(prevProps, nextProps))    .forEach(name => {      const eventType = name        .toLowerCase()        .substring(2)      dom.addEventListener(        eventType,        nextProps[name]      )    })}function commitWork(fiber){  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 workLoop(deadline) {  let shouldYield = false  while (nextUnitOfWork && !shouldYield) {    nextUnitOfWork = performUnitOfWork(nextUnitOfWork)    shouldYield = deadline.timeRemaining() < 1  }  if(!nextUnitOfWork && wipRoot){    commitRoot()  }  requestIdleCallback(workLoop)}requestIdleCallback(workLoop)function reconcileChildren(wipFiber, elements) {  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++  }}function performUnitOfWork(fiber) {  // 第一步 依据fiber节点创立实在的dom节点,并保留在fiber.dom属性中  if(!fiber.dom){    fiber.dom = createDom(fiber)  }  // 第二步 将以后fiber节点的实在dom增加到父节点中,留神,这一步是会触发浏览器回流重绘的!!!  // if(fiber.parent){  //   fiber.parent.dom.appendChild(fiber.dom)  // }  // 第三步 给子元素创立对应的fiber节点  const children = fiber.props.children  // let prevSibling  // children.forEach((child, index) => {  //   const newFiber = {  //     type: child.type,  //     props: child.props,  //     parent: fiber,  //     dom: null  //   }  //   if(index === 0){  //     fiber.child = newFiber  //   } else {  //     prevSibling.sibling = newFiber  //   }  //   prevSibling = newFiber  // })  reconcileChildren(fiber, children)  // 第四步,查找下一个工作单元  if(fiber.child){    return fiber.child  }  let nextFiber = fiber  while(nextFiber){    if(nextFiber.sibling){      return nextFiber.sibling    }    nextFiber = nextFiber.parent  }}const MiniReact = {  createElement:  (type, props, ...children) => {    return {      type,      props: {        ...props,        children: children.map(child => {          if(typeof child === 'object'){            return child          }          return {            type: 'TEXT_ELEMENT',            props: {              nodeValue: child,              children: [],            }          }        })      }    }  },  render}/** @jsx MiniReact.createElement */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>  )  MiniReact.render(element, container)}rerender("World")

第八章 Function Components

本章以上面的代码为例:

/** @jsx MiniReact.createElement */const container = document.getElementById("root")function App(props){  return <h1>Hi { props.name }</h1>}const element = <App name="foo" />MiniReact.render(element, container)

jsx通过babel编译后:

function App(props) {  return MiniReact.createElement("h1", null, "Hi ", props.name);}const element = MiniReact.createElement(App, {  name: "foo"});

函数组件有两点不同的中央:

  • 函数组件对应的fiber节点没有对应的实在dom元素
  • 须要执行函数能力获取对应的children节点,而不是间接从props.children获取

因为函数组件没有对应的fiber节点,因而在commit阶段在找父fiber节点对应的dom时,须要判断是否存在该dom元素

本章残缺代码:

import React from 'react';function createDom(fiber) {  const dom = fiber.type === 'TEXT_ELEMENT' ? document.createTextNode("") : document.createElement(fiber.type)  updateDom(dom, {}, fiber.props)  return dom}let nextUnitOfWork = nulllet wipRoot = null // 保留着对root fiber的援用let currentRoot = null // 保留着当前页面对应的fiber treelet deletions = nullfunction render(element, container){  wipRoot = {    dom: container,    props: {      children: [element], // 此时的element还只是React.createElement函数创立的virtual dom树    },    alternate: currentRoot,  }  deletions = []  nextUnitOfWork = wipRoot}function commitRoot(){  deletions.forEach(commitWork)  commitWork(wipRoot.child)  currentRoot = wipRoot  wipRoot = null}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) {  //Remove old or changed event listeners  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]      )    })  // Remove old properties  Object.keys(prevProps)    .filter(isProperty)    .filter(isGone(prevProps, nextProps))    .forEach(name => {      dom[name] = ""    })  // Set new or changed properties  Object.keys(nextProps)    .filter(isProperty)    .filter(isNew(prevProps, nextProps))    .forEach(name => {      dom[name] = nextProps[name]    })  // Add event listeners  Object.keys(nextProps)    .filter(isEvent)    .filter(isNew(prevProps, nextProps))    .forEach(name => {      const eventType = name        .toLowerCase()        .substring(2)      dom.addEventListener(        eventType,        nextProps[name]      )    })}function commitWork(fiber){  if(!fiber){    return  }  let domParentFiber = fiber.parent  while(!domParentFiber.dom){    domParentFiber = domParentFiber.parent  }  const domParent = domParentFiber.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)    commitDeletion(fiber, domParent)  }  commitWork(fiber.child)  commitWork(fiber.sibling)}function commitDeletion(fiber, domParent){  if(fiber.dom){    domParent.removeChild(fiber.dom)  } else {    commitDeletion(fiber.child, domParent)  }}function workLoop(deadline) {  let shouldYield = false  while (nextUnitOfWork && !shouldYield) {    nextUnitOfWork = performUnitOfWork(nextUnitOfWork)    shouldYield = deadline.timeRemaining() < 1  }  if(!nextUnitOfWork && wipRoot){    commitRoot()  }  requestIdleCallback(workLoop)}requestIdleCallback(workLoop)function reconcileChildren(wipFiber, elements) {  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++  }}function performUnitOfWork(fiber) {  // 1.函数组件对应的fiber节点没有实在dom元素  // 2.函数组件须要运行函数获取children  const isFunctionComponent = fiber.type instanceof Function  if(!isFunctionComponent && !fiber.dom){    fiber.dom = createDom(fiber)  }  const children = isFunctionComponent ? [fiber.type(fiber.props)] : fiber.props.children  // 第二步 为每一个新的react element节点创立对应的fiber节点,并判断旧的fiber节点上的实在dom元素是否能够复用。  // 节俭创立实在dom元素的开销  reconcileChildren(fiber, children)  // 第三步,查找下一个工作单元  if(fiber.child){    return fiber.child  }  let nextFiber = fiber  while(nextFiber){    if(nextFiber.sibling){      return nextFiber.sibling    }    nextFiber = nextFiber.parent  }}const MiniReact = {  createElement:  (type, props, ...children) => {    return {      type,      props: {        ...props,        children: children.map(child => {          if(typeof child === 'object'){            return child          }          return {            type: 'TEXT_ELEMENT',            props: {              nodeValue: child,              children: [],            }          }        })      }    }  },  render}/** @jsx MiniReact.createElement */const container = document.getElementById("root")function App(props){  return <h1>Hi { props.name }</h1>}const element = <App name="foo" />MiniReact.render(element, container)

第九章 Hooks

本章残缺代码

import React from 'react';function createDom(fiber) {  const dom = fiber.type === 'TEXT_ELEMENT' ? document.createTextNode("") : document.createElement(fiber.type)  updateDom(dom, {}, fiber.props)  return dom}let nextUnitOfWork = nulllet wipRoot = null // 保留着对root fiber的援用let currentRoot = null // 保留着当前页面对应的fiber treelet deletions = nullfunction render(element, container){  wipRoot = {    dom: container,    props: {      children: [element], // 此时的element还只是React.createElement函数创立的virtual dom树    },    alternate: currentRoot,  }  deletions = []  nextUnitOfWork = wipRoot}function commitRoot(){  deletions.forEach(commitWork)  commitWork(wipRoot.child)  currentRoot = wipRoot  wipRoot = null}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) {  //Remove old or changed event listeners  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]      )    })  // Remove old properties  Object.keys(prevProps)    .filter(isProperty)    .filter(isGone(prevProps, nextProps))    .forEach(name => {      dom[name] = ""    })  // Set new or changed properties  Object.keys(nextProps)    .filter(isProperty)    .filter(isNew(prevProps, nextProps))    .forEach(name => {      dom[name] = nextProps[name]    })  // Add event listeners  Object.keys(nextProps)    .filter(isEvent)    .filter(isNew(prevProps, nextProps))    .forEach(name => {      const eventType = name        .toLowerCase()        .substring(2)      dom.addEventListener(        eventType,        nextProps[name]      )    })}function commitWork(fiber){  if(!fiber){    return  }  let domParentFiber = fiber.parent  while(!domParentFiber.dom){    domParentFiber = domParentFiber.parent  }  const domParent = domParentFiber.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)    commitDeletion(fiber, domParent)  }  commitWork(fiber.child)  commitWork(fiber.sibling)}function commitDeletion(fiber, domParent){  if(fiber.dom){    domParent.removeChild(fiber.dom)  } else {    commitDeletion(fiber.child, domParent)  }}function workLoop(deadline) {  let shouldYield = false  while (nextUnitOfWork && !shouldYield) {    nextUnitOfWork = performUnitOfWork(nextUnitOfWork)    shouldYield = deadline.timeRemaining() < 1  }  if(!nextUnitOfWork && wipRoot){    commitRoot()  }  requestIdleCallback(workLoop)}requestIdleCallback(workLoop)function reconcileChildren(wipFiber, elements) {  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++  }}function performUnitOfWork(fiber) {  // 1.函数组件对应的fiber节点没有实在dom元素  // 2.函数组件须要运行函数获取children  const isFunctionComponent = fiber.type instanceof Function  if(!isFunctionComponent && !fiber.dom){    fiber.dom = createDom(fiber)  }  const children = isFunctionComponent ? updateFunctionComponent(fiber) : fiber.props.children  // 第二步 为每一个新的react element节点创立对应的fiber节点,并判断旧的fiber节点上的实在dom元素是否能够复用。  // 节俭创立实在dom元素的开销  reconcileChildren(fiber, children)  // 第三步,查找下一个工作单元  if(fiber.child){    return fiber.child  }  let nextFiber = fiber  while(nextFiber){    if(nextFiber.sibling){      return nextFiber.sibling    }    nextFiber = nextFiber.parent  }}let wipFiber = nulllet hookIndex = nullfunction updateFunctionComponent(fiber){  wipFiber = fiber  hookIndex = 0  wipFiber.hooks = []  return [fiber.type(fiber.props)]}function useState(initial){  const oldHook = wipFiber.alternate && wipFiber.alternate.hooks && wipFiber.alternate.hooks[hookIndex]  const hook = {    state: oldHook ? oldHook.state : initial,    queue: [],  }  const actions = oldHook ? oldHook.queue : []  actions.forEach(action => {    hook.state = action(hook.state)  })  const setState = action => {    hook.queue.push(action)    wipRoot = {      dom: currentRoot.dom,      props: currentRoot.props,      alternate: currentRoot,    }    nextUnitOfWork = wipRoot    deletions = []  }  wipFiber.hooks.push(hook)  hookIndex++  return [hook.state, setState]}const MiniReact = {  createElement:  (type, props, ...children) => {    return {      type,      props: {        ...props,        children: children.map(child => {          if(typeof child === 'object'){            return child          }          return {            type: 'TEXT_ELEMENT',            props: {              nodeValue: child,              children: [],            }          }        })      }    }  },  render,  useState,}/** @jsx MiniReact.createElement */const container = document.getElementById("root")function Counter(){  const [state, setState] = MiniReact.useState(1)  return (    <h1 onClick={() => setState(c => c + 1)}>      Count: { state }    </h1>  )}const element = <Counter />MiniReact.render(element, container)