VirtualDom

139 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第19天,点击查看活动详情 

1. 面试题:jsx是什么,是html吗?

首先jsx不是html 只是以类似html的形式来定义页面, 实际是根据写的jsx生成Virtual Dom , 然后再根据virtual dom来用js中的dom相关api生成实际页面

<div className="container">
  <div>Hello React</div>
  <p>React is great </p>
</div>

2. Virtual-Dom 是什么

virtual dom实际就是jsx转换为用key value形式来描述jsx的对象

上面的jsx对应的dom会转化为下面的形式

{
  type: "div",
  props: { className: "container" },
  children: [
    {
      type: "div",
      props: null,
      children: [
        {
          type: "text",
          props: {
            textContent: "Hello React"
          }
        }
      ]
    },
    {
      type: "p",
      props: null,
      children: [
        {
          type: "text",
          props: {
            textContent: "React is great"
          }
        }
      ]
    }
  ]
}


没错上面的对象就是虚拟dom

为什么虚拟dom能够提高效率

虚拟dom如何工作的呢 实际开发过程中频繁操作dom是最耗性能的。 实际上正式由于虚拟dom操作的便利性 我们可以对比数据是否变化,有变化, 对变化的部分的dom做对应的增删改查操作。 没变化就不会更新

before

<div className="container">
    <div>123</div>
</div>

after

<div className="container">
    <div>123</div>
</div>

对应虚拟dom

{
  type: "div",
  props: { className: "container" },
  children: [
    {
      type: "div",
      props: null,
      children: [
        {
          type: "text",
          props: {
①            textContent: "123"
          }
        }
      ]
    }
  ]
}


实际变化的就是①处的123变成了456

Virtual-Dom 如何工作

创建Virtual-Dom

babel会将jsx转化为React.createElement()形式的代码 而createElement方法则会返回对应的虚拟dom

export default function createElement(type, props, ...children) {
  const childElements = [].concat(...children).reduce((result, child) => {
    if (child !== false && child !== true && child !== null) {
      if (child instanceof Object) {
        result.push(child)
      } else {
        result.push(createElement("text", { textContent: child }))
      }
    }
    return result
  }, [])
  return {
    type,
    props: Object.assign({ children: childElements }, props),
    children: childElements
  }
}

生成对应的页面

// render.js

import mountNativeElement from "./mountNativeElement"

export default function render(
  virtualDOM,
  container,
  oldDOM = container.firstChild
) {
  mountNativeElement(virtualDOM, container)
}

// mountNativeElement.js

import createDOMElement from "./createDOMElement"

export default function mountNativeElement(virtualDOM, container) {
  const newElement = createDOMElement(virtualDOM)
  container.appendChild(newElement) // 生成的dom实例添加到container上
}

// createDOMElement.js

import mountElement from "./mountElement"
import updateElementNode from "./updateElementNode"

export default function createDOMElement(virtualDOM) {
  let newElement = null
   // 创建文本节点
  if (virtualDOM.type === "text") {
    newElement = document.createTextNode(virtualDOM.props.textContent)
  } else {// 创建元素节点
    newElement = document.createElement(virtualDOM.type)
    // 更新元素属性
    updateElementNode(newElement, virtualDOM)
  }
  // 递归渲染子节点
  virtualDOM.children.forEach(child => {
    // 因为不确定子元素是 NativeElement 还是 Component 所以调用 mountElement 方法进行确定
    mountElement(child, newElement)
  })
  return newElement
}

调用

TinyReact.render(<KeyDemo />, root)

以上便是完整的生成和渲染过程。