Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
yangrenmu opened this issue May 20, 2019 · 0 comments
Open

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

yangrenmu opened this issue May 20, 2019 · 0 comments
Labels

Comments

@yangrenmu
Copy link
Owner

yangrenmu commented May 20, 2019

前言

本文主要参考了从零开始实现一个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
附上本文代码地址

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant