我的项目地址:
https://github.com/544076724/...

之前写过个别react-mini版本的实现就是会有点问题,这里进行了一次改版,这个构造是stack版的后续会出一版filber架构的.
筹备工作是和之前一样的,参考我之前写的
https://segmentfault.com/a/11...

打包工具

npm install -g parcel-bundler

.babelrc

{    "presets": ["env"],    "plugins": [        ["transform-react-jsx", {            "pragma": "React.createElement"        }]    ]}

最初parcel index.html 启动我的项目

jsx代码咱们还是通过babel设定应用React.createElement办法来转换生成虚构dom.

对于虚构dom,我原来那一篇讲过虚构dom,这里用一句话来总结就是:

在 React 中,每个 DOM 对象都有一个对应的 Virtual DOM 对象,它是 DOM 对象的 JavaScript 对象表现形式,其实就是应用 JavaScript 对象来形容 DOM 对象信息,比方 DOM 对象的类型是什么,它身上有哪些属性,它领有哪些子元素。

能够把 Virtual DOM 对象了解为 DOM 对象的正本,然而它不能间接显示在屏幕上。
虚构dom是如何晋升效率的总结一下就是:
在 React 第一次创立 DOM 对象后,会为每个 DOM 对象创立其对应的 Virtual DOM 对象,在 DOM 对象产生更新之前,React 会先更新所有的 Virtual DOM 对象,而后 React 会将更新后的 Virtual DOM 和 更新前的 Virtual DOM 进行比拟,从而找出发生变化的局部,React 会将发生变化的局部更新到实在的 DOM 对象中,React 仅更新必要更新的局部。

Virtual DOM 对象的更新和比拟仅产生在内存中,不会在视图中渲染任何内容,所以这一部分的性能损耗老本是微不足道的。

然而首次渲染的时候因为要生成虚构dom来做映射,所以首次渲染的时候是没有原来那种形式快的,因为要做一些其余的额定操作.

这里标注一下咱们用到的办法以及作用

  • render.js :入口文件调用diff办法挂载或比对新旧vnode
  • diff.js :该办法用来做新旧vnode的比对,以及首次挂载的解决,setState时会调用,或者render(<div>sss</div) 更改render(<span>lll</span>)会用到.
  • Component.js : 类组件的父类,所有类组件继承自它,存储props,外部有setState办法,调用 diff来比照新旧vnode更新,注册一系列生命周期函数.
  • mountElement.js : 该办法用来辨别是组件还是 一般元素,一般元素就生成dom挂载到界面,组件时获取组件的虚构dom vnode 而后再挂载
  • createDOMElement.js :依据虚构dom生成实在dom,并解决props属性
  • diffComponent.js :该办法用来 比照更新组件
  • updateTextNode.js : 更新textNode 节点
  • updateNodeElement.js :该办法设置或更新实在dom上的属性
  • unmountNode.js :该办法用来删除实在dom上的节点(删除节点时如果该节点是由组件生产的,须要把对应的ref和绑定的事件函数清空掉,避免内存泄露)
  • createElement.js :该函数用来生成虚构dom,其余类react框架中也会叫h函数
  • index.js :做react入口文件,负责对立导出
  • isFunction.js :判断以后tag是不是一个组件
  • isFunctionComponent.js :该办法用来判断是函数组件还是类组件,组件原型有render办法就认为是类组件
  • mountComponent.js :该办法 用来 获取组件的虚构dom vnode,并且把组件实例挂载到vnode上,不便后续调用生命周期
  • mountNativeElement.js :该办法依据虚构vnode 来获取实在dom而后在父节点中进行 更新或增加
  • updateComponent.js :同一个组件更新操作
  • enqueueSetState.js :解决setState批量更新的操作

好了咱们上面正式进入正题,先说一下咱们的开发思路:

  1. 创立createElement办法来转换jsx为vnode
  2. 创立Component组件类,所有的组件都是继承自该类
  3. 将vnode转换为实在dom来插入父级节点(这里间接应用diff办法比照了,该办法承载了新旧vnode的比照,并且判断了是否是首次挂载vnode),diff首次挂载是会调用mountElement办法,会辨别是否是组件

    1. 不是组件得时候,那就是html节点或者文本节点,那就会应用mountNativeElement办法间接把以后这个vnode递归(递归过程中会继续应用mountElement办法来判断以后这级是组件还是间接的html标签)来转换为html片段,最初插入到指定的容器中(例如id="root"的节点)
    2. 是组件的时候调用mountComponent办法,该办法会判断是类组件还是函数组件,而后来运行他们获取他们返回的vnode,类组件时会把组件实例贮存在返回的vnode上,不便后续调用组件生命周期,而后它们解析进去的vnode,会查看是不是还是组件,例如例如function App(){ return <Demo / >} 这种,如果是的话持续调用mountComponent解析,否则调用mountNativeElement办法把vnode转换成实在dom,而后执行组件生命周期componentDidMount,给props.ref传递组件实例当援用。
  4. 到这里首次加载就实现了,而后就是diff办法中不是首次加载而是比照新旧vnode更新操作了,这里次要分几步如下:

    1. 新vnode不是组件(组件要独自解决)并且新旧vnode标签不同,这会不须要比照间接用新的vnode生成实在dom而后把老的dom替换就能够了
    2. 新vnode是组件调用diffComponent办法来比照组件更新,判断两个是否是同一个组件,如果旧的vnode不是组件或者和新vnode不是同一个组件间接用mountElement办法渲染新的dom,把旧的dom替换掉就能够,是同一个组件旧调用updateComponent来更新组件
    3. 新旧vnode是同一个节点,标签一样,文本节点的话间接更新内容,元素节点的话更新元素上的属性,也就是更新props中的属性
    4. 到这里同级的比对实现之后就是,就该进行子集的比对了

      1. 子集的比对要应用到key属性,首先获取以后实在dom html中的子节点的所有元素节点的key,存到一个对象keyedElements中,值就是以后的实在dom的援用,而后开始比对
      2. 如果从实在dom中获取的子节点就没key的话,间接一一节点 一个一个一次调用diff更新比照就能够了.这会是同级比对,不会去查找key
      3. 而后从实在dom html中获取子节点有key时,循环新vnode的子节点,获取它的key,而后从keyedElements对象中查找看能不能找到key一样的获取它的值放到domElement变量中(实在dom援用)

        1. 如果找到了就该查看地位对不对了,因为咱们是循环这会用这个i获取实在dom子集中i的地位的元素看和domElement 是不是同一个,不是的话证实地位不对,那就把domElement插入到以后这个实在dom子集中i的地位之前就能够了.
        2. 而后就是如果从keyedElements没有找到key雷同的,证实实在dom就短少这个节点,这会间接应用mountElement新建就能够了。
    5. 子集比照完了之后,就该删除多余节点了

      1. 判断旧节点的数量比新的长,没有key,间接删除,没有key时证实,到从结尾到virtualDOM.children的结尾都曾经被更新过了,所以咱们从后往前删除,删除到virtualDOM.children 的结尾处就能够了
      2. 有key时,通过key属性删除节点,把老的节点里的key从新的vnode.children里查找,如果找不到就删除

到此为止咱们的所有新建和更新就实现了。

最初的流程就是贴代码了

Component.js

import {enqueueSetState} from "./enqueueSetState";export default class Component { //类组件的父类,所有类组件继承自它  constructor(props) {    this.props = props //贮存props  }  setState(state) { //获取state    enqueueSetState(state,this)  }  setDOM(dom) { //设置 以后组件对应的实在dom    this._dom = dom  }  getDOM() {//获取    return this._dom  }  updateProps(props) { //更新props    this.props = props  }  // 生命周期函数  componentWillMount() {}  componentDidMount() {}  componentWillReceiveProps(nextProps) {}  shouldComponentUpdate(nextProps, nextState) {    return nextProps != this.props || nextState != this.state  }  componentWillUpdate(nextProps, nextState) {}  componentDidUpdate(prevProps, preState) {}  componentWillUnmount() {}}

createDOMElement.js

import mountElement from "./mountElement"import updateNodeElement from "./updateNodeElement"/** *  * @param {*} virtualDOM 依据虚构dom生成实在dom  * 这个函数,以后项 virtualDOM.tag 不会是一个办法,也就是说,函数组件不会间接走到这里 * 这里解决ref 不是组件上的ref而是  <div ref={办法()}></div> 这种,在这里ref的回调传入的不会是组件的援用 */export default function createDOMElement (virtualDOM) {  let newElement = null  if (virtualDOM.tag === "text") {    //文本节点    newElement = document.createTextNode(virtualDOM.props.textContent) //取出咱们赋值到props里的内容  } else {    newElement = document.createElement(virtualDOM.tag)    updateNodeElement(newElement, virtualDOM) //设置props属性 到实在dom  }  newElement._virtualDOM = virtualDOM //把以后的vnode对象,挂载到实在dom上,用来做新旧vnode比照应用,下次更新它就是旧的vnode了  // 这回咱们才创立了第一层节点,要递归创立子节点  virtualDOM.children.forEach(child => {    mountElement(child, newElement) //该办法会辨别 以后节点是 组件还是元素  })  //解决ref属性,如果要是 有ref的话,调用ref传入的函数,把以后创立的newElement dom传递给它  if (virtualDOM.props && virtualDOM.props.ref) {    virtualDOM.props.ref(newElement)  }  return newElement  //最初返回新建的 dom元素}

createElement.js

/** * 该函数用来生成虚构dom,其余类react框架中也会叫h函数,咱们配置了babel 会把jsx转换成React.createElement("div", null, "123")这种模式 * @param {*} tag 标签类型标记是 元素标签还是文本元素text 还是组件  为组件是 tag 是一个函数 * @param {*} props  标签 props 属性 * @param  {...any} children 以后元素的上级元素 前两个之后的都是 子元素 */export default function createElement (tag, props, ...children) {  //在这里把 所有的 布尔值和null去掉,这是不须要在界面展现的  // children 里会有如下 children 这种格局 也就是子元素有是文本元素的, 这样会类型不对立  //一会是字符串一会是对象所以在这里给它对立一下,子节点都转换成对象  // { tag: "div", props: null, children: Array(4) }  //   children: Array(4)  //       0: "hello"  //       1: { tag: "span", props: null, children: Array(1) }  //       2: "react"  //       3: { tag: "span", props: null, children: Array(1) }  //   length: 4  //   __proto__: Array(0)  //   props: null  //   tag: "div"    const childElement = [].concat(...children).reduce((result, child) => {    // 通过reduce解决    if (child !== false && child !== true && child !== null) {      if (child instanceof Object) {        result.push(child);      } else {        result.push(createElement("text", { textContent: child }));      }    }    return result;  },[])  //通过这样咱们就做了一个类型对立  return {    tag,    props:Object.assign({ children: childElement }, props), //react中props属性也有childern属性这里咱们也加上    children:childElement  }}

diff.js

import mountElement from "./mountElement"import createDOMElement from "./createDOMElement"import diffComponent from "./diffComponent"import updateTextNode from "./updateTextNode"import updateNodeElement from "./updateNodeElement"import unmountNode from "./unmountNode"/** * 该办法用来做新旧vnode的比对,以及首次挂载的解决 * setState时会调用,或者render(<div>sss</div) 更改render(<span>lll</span>) * @param {*} virtualDOM 虚构dom * @param {*} container 挂载的父级节点 * @param {*} oldDOM 虚构dom对应的实在dom,更新时才会有,初始创立时 是没有的, 更新时它也是老的实在dom */export default function diff (virtualDOM, container, oldDOM) {    const oldVirtualDOM = oldDOM && oldDOM._virtualDOM //首次创立时是不存在的  const oldComponent = oldVirtualDOM && oldVirtualDOM.component //查看该节点是否是 组件  if (!oldDOM) { //如果它不存在是首次挂载 间接执行mountElement办法    mountElement(virtualDOM, container)  }  else if (//老的存在,要比照更新    // 如果要比对的两个节点类型不雷同    virtualDOM.tag !== oldVirtualDOM.tag &&    // 并且节点的类型不是组件 因为组件要独自解决    typeof virtualDOM.tag !== "function"  ) {    // 新旧的标签不同,不须要比照    // 间接应用新的 virtualDOM 对象生成实在 DOM 对象    const newElement = createDOMElement(virtualDOM)    // 应用新的 DOM 对象替换旧的 DOM 对象    oldDOM.parentNode.replaceChild(newElement, oldDOM)  } else if (typeof virtualDOM.tag === "function") {    // 是组件    diffComponent(virtualDOM, oldComponent, oldDOM, container)  } else if (oldVirtualDOM && virtualDOM.tag === oldVirtualDOM.tag) {    //如果旧的vnode存在 并且 两个标签一样 进行节点更新    if (virtualDOM.tag === "text") {//文本节点      // 更新内容      updateTextNode(virtualDOM, oldVirtualDOM, oldDOM)    } else {      // 更新元素节点属性      updateNodeElement(oldDOM, virtualDOM, oldVirtualDOM)    }    //最初是key的比照    // 1. 将领有key属性的子元素搁置在一个独自的对象中    let keyedElements = {}    for (let i = 0, len = oldDOM.childNodes.length; i < len; i++) {      let domElement = oldDOM.childNodes[i]      if (domElement.nodeType === 1) {        let key = domElement.getAttribute("key")        if (key) {          keyedElements[key] = domElement        }      }    }    let hasNoKey = Object.keys(keyedElements).length === 0    if (hasNoKey) {//无key      // 没有key,间接逐级比对,一个一个更新      virtualDOM.children.forEach((child, i) => {        diff(child, oldDOM, oldDOM.childNodes[i]) //更新比对      })    } else {//html有key      // 2. 循环 virtualDOM 的子元素 获取子元素的 key 属性      virtualDOM.children.forEach((child, i) => {        let key = child.props.key        if (key) {          let domElement = keyedElements[key] //获取对应的实在dom          if (domElement) {            // 3. 看看以后地位的元素是不是咱们冀望的元素            //如果以后坐标的元素在 上一次操作就存在,而后查看两个是不是相等            //不相等的话,证实以后地位的元素不是 咱们要的这个domElement元素,那就间接把它插入到这个地位            if (oldDOM.childNodes[i] && oldDOM.childNodes[i] !== domElement) {              oldDOM.insertBefore(domElement, oldDOM.childNodes[i])            }          } else {            // domElement不存在证实 原来就少这个元素 间接进行新增元素操作            mountElement(child, oldDOM, oldDOM.childNodes[i])          }        }      })    }    //子集比照完了,查看旧的节点是不是比新的长,长的话证实新的没有,须要删除操作    // 获取旧节点    let oldChildNodes = oldDOM.childNodes    // 判断旧节点的数量比新的长    if (oldChildNodes.length > virtualDOM.children.length) {      if (hasNoKey) { //没有key,间接删除        // 有节点须要被删除        for ( //没有key时证实,到从结尾到virtualDOM.children的结尾都曾经被更新过了,所以咱们从后往前删除          //删除到virtualDOM.children 的结尾处就能够了,代码如下          let i = oldChildNodes.length - 1;          i > virtualDOM.children.length - 1;          i--        ) {          unmountNode(oldChildNodes[i]) //删除新的vnode下面不存在的节点        }      } else {//有key        // 通过key属性删除节点,把老的节点里的key从新的vnode.children里查找,如果找不到就删除        for (let i = 0; i < oldChildNodes.length; i++) {          let oldChild = oldChildNodes[i]          let oldChildKey = oldChild._virtualDOM.props.key          let found = false          for (let n = 0; n < virtualDOM.children.length; n++) {            if (oldChildKey === virtualDOM.children[n].props.key) { //找到了,退出本次循环              found = true              break            }          }          if (!found) { //没找到,新的vnode.children不存在,删除            unmountNode(oldChild)          }        }      }    }  }}

diffComponent.js

import mountElement from "./mountElement"import updateComponent from "./updateComponent"/** * 该办法用来 比照更新组件 * @param {*} virtualDOM  //虚构dom * @param {*} oldComponent //旧的组件 * @param {*} oldDOM //旧实在dom * @param {*} container //要渲染到的父级 */export default function diffComponent(  virtualDOM,  oldComponent,  oldDOM,  container) {  if (isSameComponent(virtualDOM, oldComponent)) {    // 同一个组件 做组件更新操作    updateComponent(virtualDOM, oldComponent, oldDOM, container)  } else {    // 不是同一个组件,间接用当初vnode 来渲染    mountElement(virtualDOM, container, oldDOM)  }}// 判断是否是同一个组件function isSameComponent(virtualDOM, oldComponent) {  return oldComponent && virtualDOM.type === oldComponent.constructor}

enqueueSetState.js

import diff from "./diff"//比照新旧vnode更新/** * 队列   先进先出 后进后出 ~ * @param {Array:Object} setStateQueue  形象队列 每个元素都是一个key-value对象 key:对应的stateChange value:对应的组件 * @param {Array:Component} renderQueue  形象须要更新的组件队列 每个元素都是Component */const setStateQueue = [];const renderQueue = [];function defer (fn) {  //requestIdleCallback的兼容性不好,对于用户交互频繁屡次合并更新来说,requestAnimation更有及时性高优先级,requestIdleCallback则适宜解决能够提早渲染的工作~  //   if (window.requestIdleCallback) {  //     console.log('requestIdleCallback');  //     return requestIdleCallback(fn);  //   }  //高优先级工作 异步的 先挂起    //通知浏览器——你心愿执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该办法须要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行  return requestAnimationFrame(fn);}export function enqueueSetState (stateChange, component) {  //第一次进来必定会先调用defer函数  if (setStateQueue.length === 0) {    //清空队列的方法是异步执行,上面都是同步执行的一些计算    defer(flush);  }  // setStateQueue:[{state:{a:1},component:app},{state:{a:2},component:test},{state:{a:3},component:app}]  //向队列中增加对象 key:stateChange value:component  setStateQueue.push({    stateChange,    component  });  //如果渲染队列中没有这个组件 那么增加进去  if (!renderQueue.some(item => item === component)) {    renderQueue.push(component);  }}function flush () {//下次重绘之前调用,合并state  let item, component;  //顺次取出对象,执行   while ((item = setStateQueue.shift())) {    const { stateChange, component } = item;    let newState;    // 如果stateChange是一个办法,也就是setState的第二种模式    if (typeof stateChange === 'function') {      newState = Object.assign(        component.state,        stateChange(component.prevState, component.props)      );    } else {      // 如果stateChange是一个对象,则间接合并到setState中      newState = Object.assign(component.state, stateChange);    }    // 如果没有prevState,则将以后的state作为初始的prevState    if (!component.prevState) {      component.prevState = Object.assign({}, newState);    }    component.state = newState;     }  //先做一个解决合并state的队列,而后把state挂载到component上面 这样上面的队列,遍历时候,能也拿到state属性  //顺次取出组件,执行更新逻辑,渲染  while ((component = renderQueue.shift())) {     // 获取最新的要渲染的 virtualDOM 对象     let virtualDOM = component.render()     // 获取旧的 virtualDOM 对象 进行比对     let oldDOM = component.getDOM()     // 获取容器     let container = oldDOM.parentNode     // 实现对象     diff(virtualDOM, container, oldDOM)  }}

index.js

import createElement from "./createElement"import render from "./render"import Component from "./Component"export default {  createElement,  render,  Component}

isFunction.js

export default function isFunction(virtualDOM) { //判断以后tag是不是一个组件  return virtualDOM && typeof virtualDOM.tag === "function"}

isFunctionComponent.js

import isFunction from "./isFunction"/** *  该办法用来判断是函数组件还是类组件,组件原型有render办法就认为是类组件 * @param {*} virtualDOM 虚构dom */export default function isFunctionComponent(virtualDOM) {  const type = virtualDOM.tag  return (    type && isFunction(virtualDOM) && !(type.prototype && type.prototype.render)  )}

mountComponent.js

import isFunctionComponent from "./isFunctionComponent"import mountNativeElement from "./mountNativeElement"import isFunction from "./isFunction"/** * 该办法 用来 获取组件的虚构domvnode * @param {*} virtualDOM 虚构dom * @param {*} container  挂载的父级节点 * @param {*} oldDOM 实在dom */export default function mountComponent (virtualDOM, container, oldDOM) {  let nextVirtualDOM = null  let component = null  // 判断组件是类组件还是函数组件  if (isFunctionComponent(virtualDOM)) {    // 函数组件    nextVirtualDOM = buildFunctionComponent(virtualDOM)  } else {    // 类组件    nextVirtualDOM = buildClassComponent(virtualDOM)    component = nextVirtualDOM.component  }  if (isFunction(nextVirtualDOM)) { //如果组件调用解析当前返回的 还是一个 组件的话,就持续解析它    //例如function App(){ return <Demo / >} 这种    mountComponent(nextVirtualDOM, container, oldDOM)  } else {    //否则 以后节点曾经解析成 元素或者文本节点了,调用mountNativeElement实在dom,挂载或更新    mountNativeElement(nextVirtualDOM, container, oldDOM)  }  if (component) { //走到这里时 组件曾经加载实现了,执行它的生命周期函数    component.componentDidMount()    if (component.props && component.props.ref) {      component.props.ref(component) //传递组件实例给ref援用          }  }}function buildFunctionComponent(virtualDOM) {//函数组件返回虚构dom  return virtualDOM.tag(virtualDOM.props || {}) }function buildClassComponent(virtualDOM) {//class组件调用render 返回虚构dom  const component = new virtualDOM.tag(virtualDOM.props || {})  component.prevState = component.state;  const nextVirtualDOM = component.render()  nextVirtualDOM.component = component //把以后组件实例挂载到虚构dom上,不便后续执行 生命周期函数  return nextVirtualDOM}

mountElement.js

import mountNativeElement from "./mountNativeElement"import isFunction from "./isFunction"import mountComponent from "./mountComponent"/** * 该办法用来辨别是组件还是 一般元素,一般元素就生成dom挂载到界面 * @param {*} virtualDOM 虚构dom,以后的vnode * @param {*} container 要放入的容器元素 * @param {*} oldDOM 旧的 实在dom,下面贮存了老的vnode,参数可选,首次渲染界面时不存在 */export default function mountElement(virtualDOM, container, oldDOM) {  if (isFunction(virtualDOM)) { //是组件    // Component    mountComponent(virtualDOM, container, oldDOM)  } else {//不是组件,依据虚构dom生成实在dom挂载    // NativeElement    mountNativeElement(virtualDOM, container, oldDOM)  }}

mountNativeElement.js

import createDOMElement from "./createDOMElement"import unmountNode from "./unmountNode"/** * 该办法依据虚构vnode 来获取实在dom而后在父节点中进行 更新或增加 * @param {*} virtualDOM 虚构dom * @param {*} container  挂载的父级节点 * @param {*} oldDOM 老的实在dom */export default function mountNativeElement(virtualDOM, container, oldDOM) {    let newElement = createDOMElement(virtualDOM) //获取实在dom  // 将转换之后的DOM对象搁置在页面中  if (oldDOM) { //如果老的dom存在    container.insertBefore(newElement, oldDOM) //插入它之前  } else {    container.appendChild(newElement) //间接增加  }  // 判断旧的DOM对象是否存在 如果存在 删除  if (oldDOM) {    unmountNode(oldDOM)  }  // 获取类组件实例对象  let component = virtualDOM.component  // 如果类组件实例对象存在  if (component) {    // 将DOM对象存储在类组件实例对象中,setState时要获取 实在dom,而后获取对应信息更新    component.setDOM(newElement)  }}

render.js

import diff from "./diff"/** *  * @param {*} virtualDOM 虚构dom * @param {*} container 挂载的父级节点 * @param {*} oldDOM 虚构dom对应的实在dom,更新时才会有,初始创立时是没有的, 咱们在创立完 实在dom之后 会把以后用的vnode * 挂载到实在dom一个属性上 不便后续做新旧vnode比照  */export default function render (  virtualDOM,  container,  oldDOM = container.firstChild) {    diff(virtualDOM, container, oldDOM) // 虚构dom diff算法,该办法 初始会生成dom新建, 之后会做新旧vnode比照}

unmountNode.js

/** * 该办法用来删除实在dom上的节点 * @param {*} node 要删除的节点  */export default function unmountNode(node) {  // 获取节点的 _virtualDOM 对象  const virtualDOM = node._virtualDOM  // 1. 文本节点能够间接删除  if (virtualDOM.type === "text") {    // 删除间接    node.remove()    // 阻止程序向下执行    return  }  // 2. 看一下节点是否是由组件生成的  let component = virtualDOM.component  // 如果 component 存在 就阐明节点是由组件生成的  if (component) {    component.componentWillUnmount()  }  // 3. 看一下节点身上是否有ref属性,在这里要置成null,避免后续还有援用该组件实例,导致无奈开释  if (virtualDOM.props && virtualDOM.props.ref) {    virtualDOM.props.ref(null)  }  // 4. 看一下节点的属性中是否有事件属性,避免 事件没有删除,导致内存透露  Object.keys(virtualDOM.props).forEach(propName => {    if (propName.slice(0, 2) === "on") {      const eventName = propName.toLowerCase().slice(0, 2)      const eventHandler = virtualDOM.props[propName]      node.removeEventListener(eventName, eventHandler)    }  })  // 5. 递归删除子节点,如果子节点是组件的话,把对他的的援用和事件办法都删掉  if (node.childNodes.length > 0) {    for (let i = 0; i < node.childNodes.length; i++) {      unmountNode(node.childNodes[i])      i--    }  }  // 最初解决完了,删除该节点  node.remove()}

updateComponent.js

import diff from "./diff"/** * 同一个组件更新操作 * @param {*} virtualDOM  * @param {*} oldComponent 组件实例 * @param {*} oldDOM 实在dom * @param {*} container  */export default function updateComponent(  virtualDOM,  oldComponent,  oldDOM,  container) {  oldComponent.componentWillReceiveProps(virtualDOM.props) //生命周期函数,props变动  if (oldComponent.shouldComponentUpdate(virtualDOM.props,oldComponent.prevState)) { //查看是否要更新    // 未更新前的props    let prevProps = oldComponent.props     oldComponent.componentWillUpdate(virtualDOM.props)//行将更新    // 组件更新props    oldComponent.updateProps(virtualDOM.props)    oldComponent.prevState = oldComponent.state //更新prevState    // 获取组件返回的最新的 virtualDOM,都更新了获取最新vnode    let nextVirtualDOM = oldComponent.render()    // 更新 component 组件实例对象,给最新的vnode来赋值 component    nextVirtualDOM.component = oldComponent    // 比对 更新    diff(nextVirtualDOM, container, oldDOM)     oldComponent.componentDidUpdate(prevProps)   }}

updateNodeElement.js

/** * 该办法设置或更新实在dom上的属性 * @param {*} newElement 实在dom对象 * @param {*} virtualDOM  新的vnode * @param {*} oldVirtualDOM  老的vnode *  */export default function updateNodeElement(  newElement,  virtualDOM,  oldVirtualDOM = {}) {  // 获取节点对应的属性对象  const newProps = virtualDOM.props || {} //获取新的props属性  const oldProps = oldVirtualDOM.props || {} //旧的vnode上props属性  Object.keys(newProps).forEach(propName => {    // 获取属性值    const newPropsValue = newProps[propName]     const oldPropsValue = oldProps[propName]    if (newPropsValue !== oldPropsValue) {      // 判断属性是否是否事件属性 onClick -> click      if (propName.slice(0, 2) === "on") {        // 事件名称        const eventName = propName.toLowerCase().slice(2)        // 为元素增加事件        newElement.addEventListener(eventName, newPropsValue)        //曾经挂载过新的处理函数了要删除原有的事件的事件处理函数        if (oldPropsValue) {          newElement.removeEventListener(eventName, oldPropsValue)        }      } else if (propName === "value" || propName === "checked") {//如果要是input属性        newElement[propName] = newPropsValue  //间接设置值      } else if (propName !== "children") { //排除子集 属性        // 其余属性都通过setAttribute解决        if (propName === "className") {          newElement.setAttribute("class", newPropsValue)        } else {          newElement.setAttribute(propName, newPropsValue)        }      }    }  })  // 判断属性被删除的状况,遍历旧的属性在新的props里查找,要是找不到证实被删除了,就也要对应删除  Object.keys(oldProps).forEach(propName => {    const newPropsValue = newProps[propName]    const oldPropsValue = oldProps[propName]    if (!newPropsValue) { //没找到,删除了      // 属性被删除了      if (propName.slice(0, 2) === "on") { //删除事件        const eventName = propName.toLowerCase().slice(2)        newElement.removeEventListener(eventName, oldPropsValue)      } else if (propName !== "children") { //排除children,其余都用removeAttribute办法解决        newElement.removeAttribute(propName)      }    }  })}

updateTextNode.js

export default function updateTextNode(virtualDOM, oldVirtualDOM, oldDOM) {//更新textNode节点  if (virtualDOM.props.textContent !== oldVirtualDOM.props.textContent) {    oldDOM.textContent = virtualDOM.props.textContent    oldDOM._virtualDOM = virtualDOM  //更新完了之后因为用了新的vnode,所以要更新一下  }}

大家能够把能够去我的仓库里把代码下载下来看一下,整体还是不太简单的,外面每个办法都有正文以及jsDOC的阐明.后续会发一篇filber架构的繁难实现。

本文内容借鉴于拉钩大前端训练营