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大概是这样的
这个和我们自己
React.createElement创建的VDOM有些许不同,我们需要禁用掉新的jsx转化方式。
查阅文档@babel/plugin-transform-react-jsx后,我们可以看到直接在需要禁用新的转化方式的文件头部加上注解/** @jsxRuntime classic */即可