Skip to main content

JSX

简介

在代码中同时包含 JS 和 HTML 标记,这种写法就是 React 的「模版语言」JSX。

JSX 并不是一个新的模版语言,而是一个语法糖,可以用 JS 的方式来实现。使用 React.createElement() 这个 API 来创建一个组件的实例。

例如:

import { useState } from 'react';

const App = () => {
const [count, setCount] = useState(0);

return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>add</button>
</div>
);
};

export default App;

调用 createElement 创建一个 React 元素,具有 typepropschildren

import { createElement, useState } from 'react';

const App = () => {
const [count, setCount] = useState(0);

return createElement(
'div',
null,
createElement('h1', null, count),
createElement(
'button',
{
onClick: function () {
return setCount(count + 1);
}
},
'add'
)
);
};

export default App;

在实际开发中,不推荐使用 React.createElement() 这种方式,在官方中也将其列入「旧版 API」中,使用 JSX 更简洁易读。

JSX 规则

  1. 返回单个根元素
  2. 所有标签要闭合
  3. 采用驼峰命名

将 HTML 转换为 JSX 的转换器:https://transform.tools/html-to-jsx

原理

浏览器无法直接理解 JSX,需要通过 Babel 和 TypeScript 转换为 JS。

Babel 编译,@babel/plugin-transform-react-jsx

老版本的 React 中,为什么写 jsx 的文件要默认引入 React?

import React from 'react';
function App() {
return <div>hello,world</div>;
}

因为 JSX 被 Babel 编译后,会变成 React.createElement()形式,所以需要引入 React,防止找不到 React 引起报错。

为何要返回单个根元素?

在 React 中,组件的返回值必须是一个根元素。如下所示都是错误的:

function Foo() {
return (
<div>1</div>
<div>2</div>
)
}

function Bar() {
return (
<div>
{isOpen && (
<div>1</div>
<div>2</div>
)}
</div>
)
}

前面说了,JSX 是 React.createElement() 的语法糖。Babel 会将 Foo 组件转换为下面这样:

function Foo() {
return React.createElement('div', null, '1')
React.createElement('div', null, '2');
}

这里试图返回两个东西,这不符合 JS 语法。

注释

在 JSX 中写注释不能单独使用//,需要这么写 {/* 注释 */}

htmlFor

label标签不能使用for,要用htmlFor

<label htmlFor="zgh"></label>

className

为了防止和 js 中的class类名冲突,需要将class写成className,小驼峰命名

<input className="input" />

循环遍历

JSX 中默认对数组进行 join() 操作,如下代码会在页面上显示“abc”

function List() {
const arr = ['a', 'b', 'c'];
return <div>{arr}</div>;
}

利用原生 js 写法,使用 map 遍历数组,因为 map 有返回值,不能使用 forEach

在 map 方法中的元素需要设置 key 属性,key 最好是唯一的字符串,没得选只能用元素索引 index,但是这样做会导致性能变差,还可能引起组件状态的问题

function List() {
const arr = ['a', 'b', 'c'];
return (
<>
<ul>
{arr.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</>
);
}

也可以把遍历的逻辑抽离出来

const arr = ['a', 'b', 'c'];
function Li() {
return arr.map((item, index) => <li key={index}>{item}</li>);
}

function List() {
return (
<>
<ul>
<Li />
</ul>
</>
);
}

条件判断

  1. 条件语句:if、switch
  2. 三元运算符
  3. 逻辑运算符:&&||!

在 JSX 的大括号中不会渲染的值:布尔值、空字符、null、undefined、对象、函数

warning

{0 && <div>react</div>}会渲染出 0

如果是列表,可以用长度判断:{data.length > 0 && <div>react</div>}

性能优化

利用 children 属性避免重复渲染子元素。

如下例子,每当 App 组件被渲染时,OtherComponent组件都会被重新渲染。

import { useEffect, useState } from 'react';

const OtherComponent = () => {
console.log(123);
return <>123</>;
};

const App = () => {
const [count, setCount] = useState(0);

useEffect(() => {
const timer = setInterval(() => setCount(e => ++e), 1000);
return () => {
clearInterval(timer);
};
}, []);

return (
<>
<h1>{count}</h1>
<OtherComponent />
</>
);
};

export default App;

优化:将 OtherComponent组件作为 App 组件的子元素,这样 App 组件被渲染时,OtherComponent组件不会被重新渲染。

const OtherComponent = () => {};

const App = ({ children }) => {
// 其他省略

return (
<>
<h1>{count}</h1>
{children}
</>
);
};

const Container = () => (
<App>
<OtherComponent />
</App>
);

export default Container;