2.初识React之VDOM生成

396 阅读3分钟

React的虚拟DOM生成

虚拟dom,实际上就是js对象,只是这个对象描叙了dom结构。那么我们的目标就是需要将jsx转化为js对象,这个操作该什么时候处理比较合适呢?

在上一篇认识jsx中,我们了解到最后会通过babel转化为函数形式的代码

原jsx代码

<h1 className='title' style={{ color: "red" }} onClick={() => {console.log("哈哈哈")}}>
    hello  <span>word</span>
</h1>

转化后代码

React.createElement("h1", {
  className: "title",
  style: {
    color: "red"
  },
  onClick: () => {
    console.log("哈哈哈");
  }
}, "hello  ", /*#__PURE__*/React.createElement("span", null, "word"));

以上代码我们可以注意到,其实babel只帮我们将jsx语法转化为了函数,还不是一个对象,所以我们可以创建React.createElement方法将内容进行转化

实现React.createElement方法

  • 创建react.js文件

  • 导出React对象

  • React对象内部有createElement方法

react.js

function createElement(type, attrs, ...children){
    
}

const React = {
    createElement
}

export default React;

分析:

这个结构来源于转化后代码来的,参数简单解析一下

  • type: 标签,组件就是组件名称
  • attars:属性
  • children:子集,可能是文本数字,也可能是一个元素对象

分析需要做些什么?

  • 我们需要将方法转化为一个对象
  • 将ref,key属性提取出来
  • 将参数放在props中
import { wrapToVdom } from "../utils";


/**
 * 将其转化为虚拟dom (JS对象)
 * @param {* 标签类型} type 
 * @param {* 属性} attrs 
 * @param {* 子节点} children 
 */
function createElement(type, attrs, ...children) {
    let ref;
    let key;

    // 将key和ref两个属性提取出来
    if (attrs) {
        // 这两个属性是babel帮我们自动生成的,我们排除掉
        delete attrs.__source;
        delete attrs.__self;
        ref = attrs.ref;
        key = attrs.key;
        delete attrs.ref;
        delete attrs.key;
    }

    // 其他内容作为props传下去
    let props = { ...attrs };

    // 直接将所有的子元素放入props的children属性中
    props.children = children.map(wrapToVdom);

    return {
        ref, key, props, type
    }
}

const React = {
    createElement
}


export default React;

关于删除的自动生成属性的文档: @babel-preset-react

这里使用了一个wrapToVdom的函数,主要作用是将字符串和数字类型也变成一个虚拟DOM,不再是函数形式

const REACT_TEXT = Symbol("REACT_TEXT");

/**
 * 将元素包装一下,如果是字符串或者number就包装成虚拟dom对象
 * 这样方便diff算法
 * @param {*} element 
 * @returns 
 */
export function wrapToVdom(element) {
    return typeof element === "string" || typeof element === "number" ? { 
        type: REACT_TEXT,
        props: { content: element }
    } : element
}

我们来看看下面这种情况

源代码

<h1 className='title' style={{ color: "red" }} onClick={() => {console.log("哈哈哈")}}>
  <div>hahahaha <span>word</span></div>
</h1>

转化

/*#__PURE__*/
React.createElement("h1", {
  className: "title",
  style: {
    color: "red"
  },
  onClick: () => {
    console.log("哈哈哈");
  }
}, /*#__PURE__*/React.createElement("div", null, "hahahaha ", /*#__PURE__*/React.createElement("span", null, "word")));

注意这里React.createElement方法的第三个参数又有一个React.createElement,那么执行是怎么样的呢?

其实这个时候我们只要思考一个问题就好了。

我要执行外层React.createElement函数的时候,我是不是要获取到所有参数,如果没有所有参数,函数怎么执行呢?所以是不是应该将内层的React.createElement先执行才能拿到对应的数据,所以这个执行时由内而外

到此我们就拿到了我们所需要的vdom了,接下来就要看render方法了

注意

有可能你会发现你的代码根本不进入你写的React.createElement方法,这个是因为在React17更新的时候做了一个变化,具体可以进去了解一下,使用新的编译方式后,我们得到的VDOM大概是这样的

1-VDOM.png 这个和我们自己React.createElement创建的VDOM有些许不同,我们需要禁用掉新的jsx转化方式。

查阅文档@babel/plugin-transform-react-jsx后,我们可以看到直接在需要禁用新的转化方式的文件头部加上注解/** @jsxRuntime classic */即可