实现一个react系列一:JSX和虚拟DOM

前言

本文主要参考了从零开始实现一个React从 0 到 1 实现React
工作中经常使用react,对于react中的一些虚拟DOM、生命周期、组件等概念知其然,不知其所以然。虽然知道这些怎么用的就足够应付大部分的工作,但是作为一个开发者,还是要有追求的。所以有了这个系列,一步一步实现一个简单的react出来。

语法糖JSX

我们平时在react中写的JSX,其实是一种语法糖,会被babel转换成React.createElement()。我们可以在babel官网上做个实验,看下JSX会被babel转换成什么。下面是个简单的例子。

我们可以看到,JSX会被转成React.createElement(tag, attrs, child1, child2, ...)这种形式。那我们只要装个babel插件,然后写个createElement方法就可以处理JSX代码了。

createElement方法和虚拟DOM

  • 准备

首先安装babel模块,babel.babelrc配置如下:

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

使用打包工具parcel,webpack也可以。比较懒,使用了parcel来打包代码。

  • 实现

上面我们已经提到了JSX会被babel转换为React.createElement(tag, attrs, child1, child2, ...)
第一个参数:是元素的标签名,可以是div、span等。
第二个参数:是元素的属性名,可以是classNameonClick等。
之后的参数,是元素的子节点。
所以我们实现一个函数createElement,接受上面的参数,然后将这些参数返回就可以了。

const createElement = (tag, attrs, ...childs) => {
  return { tag, attrs, childs }
}

看下效果如何。

const React = {
  createElement
}

const title = (
  <div className="title">
    <p>Hello, world!</p>
  </div>
)
console.log(title)

打开Chrome的控制台,我们可以看到差不多是我们想要的。


createElement方法返回的对象就是虚拟DOM,这个对象中记录了该DOM节点的所有信息,根据这些信息我们可以将虚拟DOM转化为真实的DOM

将虚拟DOM渲染成真实DOM

在react中,将vdom渲染成真实的DOM,我们使用的是ReactDOM.render,像这样。

ReactDOM.render(
    <div>Hello, world!</div>, // 这个会被转化为vdom
    document.getElementById('root') // 获取根节点
)

我们可以看出,render实现的功能是将vdom转化为真实的dom,挂载到根节点上。明白这一点,代码就很好写了。

const ReactDom = {
  render
}
// 将 vdom 转换为真实 dom
const render = (vdom, root) => {
  if (typeof vdom === "string") {
    // 子元素如果是字符串,直接拼接字符串
    root.innerText += vdom
    return
  }
  const dom = document.createElement(vdom.tag)
  if (vdom.attrs) {
    for (let attr in vdom.attrs) {
      const value = vdom.attrs[attr]
      setAttribute(dom, attr, value)
    }
  }
  // 遍历子节点
  vdom.childs.forEach(child => render(child, dom))
  // 将子元素挂载到其真实 DOM 的父元素上
  root.appendChild(dom)
}
// 设置 dom 节点属性
const setAttribute = (dom, attr, value) => {
  if (attr === "className") {
    attr = "class"
  }
  // 处理事件
  if (/on\w+/.test(attr)) {
    attr = attr.toLowerCase()
    dom[attr] = value || ""
  } else if (attr === "style" && value) {
    // 处理 style 样式,可以是个字符串或者对象
    if (typeof value === "string") {
      dom.style.cssText = value
    } else if (typeof value === "object") {
      for (let styleName in value) {
        dom.style[styleName] =
          typeof value[styleName] === "number"
            ? value[styleName] + "px"
            : value[styleName]
      }
    }
  } else {
    // 其他属性
    dom.setAttribute(attr, value)
  }
}

这样我们就将虚拟dom渲染成真实的dom,考虑到热更新,我们需要在render之前先清除下root节点下的内容。

const ReactDOM = {
  render: (vdom, root) => {
    root.innerText = ""
    render(vdom, root)
  }
}

总结

react中,jsx会被babel转化为React.createElement(标签、属性、子元素1、子元素2、...)形式,该函数返回一个对象,即虚拟dom。然后ReactDOM.render(),会将虚拟dom转化为真实的dom
附上本文代码地址

posted @ 2019-05-20 23:49  yangrenmu  阅读(646)  评论(0编辑  收藏  举报