zoukankan      html  css  js  c++  java
  • 实现一个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 (/onw+/.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
    附上本文代码地址

  • 相关阅读:
    NodeJS笔记:处理非utf8编码
    SQL Server存储过程中的异常处理
    "岛主" 同学给我出的算法题
    学 Win32 汇编[18]: 关于压栈(PUSH)与出栈(POP) 之二
    如何在数据表中存取图片 回复 "三足乌" 的问题
    学 Win32 汇编[19]: 查看二进制等相关函数
    如何删除动态数组的指定元素 回复 "Splendour" 的部分问题
    学 Win32 汇编[17]: 关于压栈(PUSH)与出栈(POP) 之一
    学 Win32 汇编[22] 逻辑运算指令: AND、OR、XOR、NOT、TEST
    学 Win32 汇编[20]: 洞察标志寄存器
  • 原文地址:https://www.cnblogs.com/yangrenmu/p/10897388.html
Copyright © 2011-2022 走看看