React.createElement 与 JSX
1. 内容摘要
本文介绍
- document.createElement, React.createElement 与 JSX 三种方法创建 DOM 的比较
- JSX 的 div 标签包裹并列元素的限制
- 突破 JSX 的 div 包裹限制的两种方法
2. DOM 向 JSX 的演进
网页由 DOM 元素构成。React DOM 并不是浏览器的 DOM,而是 React DOM 只是用来告诉浏览器如何创建 DOM 的方法。通常情况下,我们并不需要 React 就能创建出一个 DOM 元素,但是 React 创建与管理 DOM 的方式有组件化、虚拟 DOM 等更高层次的抽象,由此带来的优势是更快的渲染速度,以及更好的维护性,因此值得去尝试。
下面分别用 JavaScript 原生方法,React.createElement 方法,以及 JSX 方法来创建一个 div 元素:
html结构应该如下:
hello world
2.1 document.createElement
JavaScript原生方法,没有过多需要解释的部分:
const box = document.createElement('div');
box.innerHTML = 'hello world';
box.id = 'box';
document.body.appendChild(box);
2.2 React.creaetElement
// 方法2:React.createElement
import React from 'react';
import ReactDOM from 'react-dom';
const Box = React.createElement("div", {id: "box"}, "hello world");
ReactDOM.render(Box, document.getElementById('root'));
其中 createElement(type, props, children)
- type:表示元素的类型,比如:h1, div 等。
- props:表示该元素上的属性,使用 JavaScript 对象方式表示。
- children:表示该元素内部的内容,可以是文字,可以继续嵌套另外一个
React.createElement(type, props, children)
。
其中 children 可以是一个 React.createElement 列表
,也可以写成多个参数:
var child1 = React.createElement('li', null, 'one');
var child2 = React.createElement('li', null, 'two');
var content = React.createElement('ul', { className: 'teststyle' }, [child1, child2]);
ReactDOM.render(
content,
document.getElementById('app')
);
// 或者
var child1 = React.createElement('li', null, 'one');
var child2 = React.createElement('li', null, 'two');
var content = React.createElement('ul', { className: 'teststyle' }, child1, child2);
ReactDOM.render(
content,
document.getElementById('example')
);
这个使用方式是 React.createElement 使用方法的一种,它一共有7个重载方法,上面的使用方式的函数签名为:
function createElement(
type: FunctionComponent
| ComponentClass
| string,
props?: Attributes & P | null,
...children: ReactNode[]
): ReactElement
;
其他重载方法见 React 源码,在实际开发中 React.createElement 用的比较少,因为可以直接 JSX 语法。
3. JSX(语法糖)规则
import React from 'react';
import ReactDOM from 'react-dom';
const Box = function () {
return (
hello world
)
}
ReactDOM.render(Box, document.getElementById('root'));
有 JSX 的地方,在文件开头就需要引入 React,因为实际上 JSX 是使用了 React.createElement,JSX 只是一个JS 的语法糖,所以需要引入 React 包,否则会报错。
react-dom
是一个把 React 代码渲染到网页端的包。如果在 Native 中渲染,就需要使用 React Native 的相关包。
const Box = function () {
return (
hello world
)
}
上面这一段 JSX 语法糖会被编译为:
const Box = () => {
return React.createElement("div", {id: "box"}, "hello world");
}
3.1 JSX的限制(根节点包裹)
使用 JSX 的一个限制是:在 JSX 中 html 代码第一层只能写一个元素。如果有多个标签(元素)并列,形成所谓的相邻JSX元素(adjacement jsx elements),就会报语法错误。通常这种多元素并列的情况,需要在它们外面包裹一层 div。
// error: Adjacent JSX elements must be wrapped in an enclosing tag
import React from 'react';
import ReactDOM from 'react-dom';
const title = (
Parallel elements demo
Content
);
ReactDOM.render(title, document.getElementById('root'));
// success
import React from 'react';
import ReactDOM from 'react-dom';
const title = (
Parallel elements demo
Content
);
ReactDOM.render(title, document.getElementById('root'));
3.2 突破 JSX 标签包裹限制
对于 jsx 外层需要包裹一层 div,如果要突破这个限制,目前有两种方法:
- 返回数组
- 使用高阶组件做辅助
(1) 返回数组
// 使用数组没有问题
import React from 'react';
import ReactDOM from 'react-dom';
const arr = ['Adams', 'Bill', 'Charlie'];
const Arr = () => {
return arr.map((item, index) => {item}
);
};
ReactDOM.render( , document.getElementById('root'));
(2) 高阶组件(HOC)
div 去包裹并列元素的痛点是,我们可能并不需要这个多余的 div 标签,可能会破坏 html 结构,也许上层做了 flex,并不能有效的传递到这些并列标签上。
所以这里引入了用于辅助的高阶组件 hoc。虽然高阶组件的名字听起来很吓人,然而做的事情很简单,就是传递的作用。
// 突破JSX标签包裹限制:方法2 高阶组件
import React from 'react';
import ReactDOM from 'react-dom';
const Aux = props => props.children;
const box = (
Parallel elements demo
Content
);
ReactDOM.render(box, document.getElementById('root'));
在上面这段代码中,const Aux = props => props.children
就是高阶组件。Aux 这个高阶组件的作用是把标签包括的内容进行传递和显示,查看最终 html 结构会发现 div 已经消失了,而且代码没有 div 也能正常。
在React 16.2 提供一个 Aux 功能,即 Fragment,但写成 <>>:
import React from 'react';
import ReactDOM from 'react-dom';
const title = (
<>
hello
world
>
);
ReactDOM.render(title, document.getElementById('root'));
4. 其他
4.1 初学者容易犯错
一个初学者经常犯错的问题是将 React.createElement
返回值(类型为ReactElement)当作标签使用:
// ReactElement
const Header = (
<>
hello
world
>
);
// Functional Component
const Card = () => {
return hello
}
// error
ReactDOM.render( , document.getElementById('root'));
ReactDOM.render(Card, document.getElementById('root'));
// success
ReactDOM.render(Header, document.getElementById('root'));
ReactDOM.render( , document.getElementById('root'));
4.2 babel 编译 JSX 例子
const Card = React.createElement('div', {id: 'box'}, 'hello world')
const Box = function () {
return (
hello world
)
}
function App() {
return (
{ Card }
);
}
会被处理为:
"use strict";
var Card = React.createElement('div', {
id: 'box'
}, 'hello world');
var Box = function Box() {
return /*#__PURE__*/React.createElement("div", {
id: "box"
}, "hello world");
};
function App() {
return /*#__PURE__*/React.createElement("div", {
className: "App"
}, Card, /*#__PURE__*/React.createElement(Box, null));
}
4.3 React.createElement 返回实例对象属性
const div = React.createElement('div', { id: 'box'}, 'test');
console.log(div)
其对象属性如下:
4.4 React.createElement 所有重载方法
function createElement(
type: "input",
props?: InputHTMLAttributes & ClassAttributes | null,
...children: ReactNode[]): DetailedReactHTMLElement, HTMLInputElement>;
function createElement, T extends HTMLElement>(
type: keyof ReactHTML,
props?: ClassAttributes & P | null,
...children: ReactNode[]): DetailedReactHTMLElement;
function createElement
, T extends SVGElement>(
type: keyof ReactSVG,
props?: ClassAttributes & P | null,
...children: ReactNode[]): ReactSVGElement;
function createElement, T extends Element>(
type: string,
props?: ClassAttributes & P | null,
...children: ReactNode[]): DOMElement;
// Custom components
function createElement
(
type: FunctionComponent
,
props?: Attributes & P | null,
...children: ReactNode[]): FunctionComponentElement
;
function createElement
(
type: ClassType
, ClassicComponentClass
>,
props?: ClassAttributes> & P | null,
...children: ReactNode[]): CElement>;
function createElement
, C extends ComponentClass
>(
type: ClassType
,
props?: ClassAttributes & P | null,
...children: ReactNode[]): CElement;
function createElement
(
type: FunctionComponent
| ComponentClass
| string,
props?: Attributes & P | null,
...children: ReactNode[]): ReactElement
;
```