JSX

JSX 的全称是 JavaScript XML。在 React 中,JSX 是 JavaScript 的一个语法扩展,它允许你在 JavaScript 代码中写类似于 HTML 的标记。JSX 是 React 元素的语法糖,它让创建 UI 变得直观和简单。在编译过程中,这些标记会被转换为 React.createElement 调用。

下面是 JSX 的一些特点:

  1. 声明式创建 UI:可以像编写 HTML 那样编写 JSX,但其背后是 JavaScript 的强大功能。
  2. 可嵌入 JavaScript 表达式:可以在 JSX 中嵌入任何有效的 JavaScript 表达式,只需将其放入大括号 {} 中即可。
  3. 组件和 HTML 标签的结合:在 JSX 中,可以像使用 HTML 标签一样使用 React 组件。
  4. 属性使用 CamelCase 命名法:JSX 中的属性采用驼峰命名法(CamelCase),而不是 HTML 中的小写。例如,class 变成了 classNametabindex 变成了 tabIndex
  5. 条件与循环:可以使用 JavaScript 的逻辑运算符 && 进行条件渲染,使用 map 函数遍历数组并渲染列表。
  6. 防止注入攻击:JSX 自动转义任何内部的值,这意味着在 JSX 中不会意外插入未转义的内容。React DOM 在渲染所有输入内容之前,默认会进行转义。它确保不会遭受到 XSS(跨站脚本)攻击。
  7. 与虚拟 DOM 的结合:JSX 与 React 的虚拟 DOM 概念紧密结合,使得组件状态和 DOM 的更新过程更高效。
可嵌入 JavaScript 表达式

在 JSX 中,几乎所有有效的 JavaScript 表达式都可以嵌入其中。这些表达式需要被大括号 {} 包围。以下是一些可以嵌入 JSX 中的 JavaScript 表达式示例:

  1. 变量:你可以嵌入变量来显示其值。

    1
    2
    const name = 'World';
    const element = <div>Hello, {name}!</div>;
  2. 运算符:可以使用 JavaScript 的算术运算符、比较运算符等。

    1
    const element = <div>{1 + 1}</div>; // 显示 2
  3. 函数调用:你可以调用 JavaScript 函数,并在 JSX 中显示结果。

    1
    2
    3
    4
    5
    function formatName(user) {
    return user.firstName + ' ' + user.lastName;
    }
    const user = { firstName: 'Harper', lastName: 'Perez' };
    const element = <div>{formatName(user)}</div>;
  4. 条件表达式:可以使用三元运算符或逻辑与(&&)进行条件渲染。

    1
    2
    const isLoggedIn = true;
    const element = <div>{isLoggedIn ? 'Welcome back!' : 'Please sign in.'}</div>;
    1
    2
    3
    4
    5
    6
    const messages = ['React', 'Re: React', 'Re:Re: React'];
    const element = (
    <div>
    {messages.length > 0 && <h2>You have {messages.length} unread messages.</h2>}
    </div>
    );
  5. 映射(Map):可以使用数组的 map() 方法渲染列表。

    1
    2
    3
    4
    5
    6
    7
    8
    const items = ['Apple', 'Banana', 'Cherry'];
    const element = (
    <ul>
    {items.map((item) => (
    <li key={item}>{item}</li>
    ))}
    </ul>
    );
  6. 对象属性:可以访问对象的属性。

    1
    2
    const user = { name: 'Chris' };
    const element = <div>{user.name}</div>;
  7. 组件属性:可以将表达式作为属性值传递给组件。

    1
    2
    const props = { firstName: 'Phil', lastName: 'Johnson' };
    const element = <Greeting {...props} />;

需要注意的是,虽然大部分 JavaScript 表达式都可以在 JSX 中使用,但 JSX 内部不能包含语句,比如 if 语句或 for 循环。这些更复杂的逻辑应该在 JSX 之外或使用 JavaScript 表达式的其他结构(如三元运算符或数组的 map() 方法)来完成。

JSX中不同数据类型在页面中展示什么

在 JSX 中使用大括号 {} 嵌入不同数据类型时,页面上将如何展示这些数据取决于数据的类型:

  1. 字符串(String)
    嵌入的字符串将会被显示为文本内容。

    1
    2
    const string = 'Hello World!';
    <div>{string}</div> // 页面上显示:Hello World!
  2. 数字(Number)
    数字会被直接渲染到页面上。

    1
    2
    const number = 123;
    <div>{number}</div> // 页面上显示:123
  3. 布尔值(Boolean)
    布尔值(truefalse)本身不会被渲染到页面上。布尔值通常用于条件渲染。

    1
    2
    const bool = true;
    <div>{bool}</div> // 页面上不显示任何内容
  4. null 和 undefined
    nullundefined 也不会渲染任何内容到页面上。

    1
    2
    const undef = undefined;
    <div>{undef}</div> // 页面上不显示任何内容
  5. 数组(Array)
    数组会被展开,并且数组中的每个元素都会被渲染到页面上。如果数组元素是字符串或数字,它们会依次显示。数组中的 nullundefined 和布尔值会被忽略。

    1
    2
    const array = ['Hello', ' ', 'World', '!', null, undefined, false];
    <div>{array}</div> // 页面上显示:Hello World!
  6. 对象(Object)
    直接尝试渲染一个普通对象(不是 React 组件或者能被 React 特殊处理的对象,比如 style)将导致一个错误。对象不能被直接转换为一个字符串来显示。

    1
    2
    const obj = { hello: 'world' };
    <div>{obj}</div> // 抛出错误,对象不能作为子元素
  7. React元素
    React元素会被正常渲染成对应的DOM节点。

    1
    2
    const element = <span>Hello World!</span>;
    <div>{element}</div> // 页面上显示Hello World!文本,包围在一个span标签内

在使用 JSX 时,非字符串类型的数据通常用于动态渲染组件的内容或属性,但是直接渲染对象(除了 React 元素或可序列化为字符串的对象,如日期)会导致错误。因此,在把数据嵌入到 JSX 中时,需要确保数据是可以正确显示的类型或者正确地处理这些数据。

给元素设置样式

在 React 中给元素设置样式时,通常遵循以下规则:

  1. 内联样式
    使用内联样式时,应该传递一个样式对象到元素的 style 属性。样式对象中的属性使用驼峰命名法,而不是短横线分隔命名法(例如 backgroundColor 而不是 background-color),并且属性值应当是字符串(对于非数值属性)或数字(对于像素值,React 会自动将数字转换为带有 “px” 单位的字符串)。

    1
    2
    3
    4
    5
    6
    7
    const divStyle = {
    color: 'blue',
    backgroundColor: 'lightgray', // 注意驼峰命名法
    padding: 20, // 这会变成 'padding: 20px'
    };

    <div style={divStyle}>Styled Content</div>
  2. CSS 类
    使用 CSS 类时,在 JSX 中应使用 className 属性而不是 class,因为 class 是 JavaScript 的保留关键字。然后,你可以在你的 CSS 文件中定义样式规则。

    1
    2
    3
    4
    5
    6
    7
    8
    // 在对应的 CSS 文件中
    .myClass {
    color: 'red';
    margin: '10px';
    }

    // 在 JSX 中
    <div className="myClass">Styled Content</div>
  3. 条件样式
    当你需要根据组件的状态或属性条件性地应用样式时,你可以在 classNamestyle 中使用 JavaScript 表达式来决定。

    1
    2
    3
    4
    5
    // 使用内联样式
    <div style={{ color: isActive ? 'green' : 'black' }}>Active Content</div>

    // 使用 CSS 类
    <div className={isActive ? 'activeClass' : 'inactiveClass'}>Active Content</div>
  4. CSS-in-JS 库
    使用如 styled-components 或 emotion 这类 CSS-in-JS 库时,你可以创建组件,它们具有与之关联的样式。这些库提供了更动态和强大的方式来写样式。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 使用 styled-components 为例
    import styled from 'styled-components';

    const StyledDiv = styled.div`
    color: blue;
    background-color: lightgray;
    padding: 20px;
    `;

    <StyledDiv>Styled Content</StyledDiv>
  5. 全局 CSS
    你也可以像在传统的 HTML 和 CSS 开发流程中那样,为整个应用定义全局样式。在这种情况下,你只需确保在组件中使用正确的 className 即可。

这些规则提供了灵活性,在创建 React 应用时可以根据需求和偏好选择合适的样式解决方案。

JSX上手

基于变量的参数控制元素隐藏/展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import ReactDOM from 'react-dom/client';

const root = ReactDOM.createRoot(document.getElementById('root'));
let isShow = true;
root.render(
<div>
{/* 根据变量控制css展示/隐藏元素,元素已创建 */}
<div style={{
display: isShow ? 'block' : 'none'
}}>Hello World</div>
{/* 根据变量控制元素展示/隐藏,条件满足创建元素 */}
{isShow && <div>你好,世界</div>}
</div>
);

模拟接口数据动态生成虚拟DOM

1
2
3
4
5
6
7
8
9
10
11
import ReactDOM from 'react-dom/client';

const root = ReactDOM.createRoot(document.getElementById('root'));
let arrList = [{name:'张三',age:18},{name:'李四',age:20},{name:'王五',age:22}];
root.render(
<div>
{arrList.map((item,index) => {
return <div key={index}>{item.name}-{ item.age }</div>
})}
</div>
);

没有数组,需要循环五次创建元素

1
2
3
4
5
6
7
8
9
10
11
import ReactDOM from 'react-dom/client';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<div>
{/* new Array(5)创建的数组为稀疏数组,没有map方法,需要使用fill方法填充为密集数组后才能使用map方法 */}
{new Array(5).fill(null).map((_,k) => {
return <div key={k}>hello world</div>;
})}
</div>
);

JSX底层处理逻辑

JSX(JavaScript XML)是一种看似将HTML嵌入到JavaScript中的语法糖。其底层处理逻辑涉及到转换和创建虚拟DOM结构。在React中,当你使用JSX编写组件时,这些JSX表达式实际上会被Babel之类的编译器转换为React API调用。

下面是这个过程的简化描述:

  1. 编译时转换
    JSX在编译时会被转换成React.createElement()调用。例如,当你写下如下的JSX:

    1
    const element = <div className="my-div">Hello, world!</div>;

    Babel将会把它转换为:

    1
    2
    3
    4
    5
    const element = React.createElement(
    'div',
    { className: 'my-div' },
    'Hello, world!'
    );

    这个React.createElement函数接受至少三个参数:元素类型(如'div'或组件),一个属性对象(props),以及任意数量的后续参数,表示子元素。

  2. 虚拟DOM元素
    React.createElement函数不是创建实际的DOM元素,而是返回了一个轻量级的虚拟DOM元素(VNode)。这个对象是一个普通的JavaScript对象,描述了要渲染到DOM中的结构。

    1
    2
    3
    4
    5
    6
    7
    const element = {
    type: 'div',
    props: {
    className: 'my-div',
    children: 'Hello, world!'
    }
    };
  3. 渲染
    虚拟DOM元素是React渲染到页面上的蓝图。React会将虚拟DOM树与实际DOM树进行对比,并计算出最高效的方法来更新DOM,这个过程称为”协调”(Reconciliation)。

  4. 协调与Diff算法
    当组件状态或属性发生变化时,一个新的虚拟DOM树就会被创建。React会对比新的树和上一次渲染的旧树,找出两者之间的不同,并且只更新实际DOM中需要改变的部分。这个过程中使用了高效的Diff算法。

  5. 批量更新与异步渲染
    React还实现了批量更新和异步渲染机制,以避免不必要的渲染和性能损耗。它将虚拟DOM的更新操作积累起来,然后一次性执行以提升效率。

  6. Hooks和组件状态
    在函数组件中,Hooks例如useStateuseEffect允许你在不编写类的情况下使用状态和生命周期特性。这些钩子函数在组件内部维护了状态和副作用的相关逻辑。

JSX的这些处理逻辑使得开发人员可以用一种声明式的方式来描述用户界面,而无需担心如何手动操作DOM,或者如何高效地进行DOM更新。这大大简化了前端界面的开发工作。

简单实现createElement

React.createElement() 是 React 中用于创建虚拟 DOM 元素的方法。虚拟 DOM 是一个普通的 JavaScript 对象,它描述了 DOM 结构。当 React 渲染一个组件时,它会使用这个虚拟 DOM 来高效地更新 web 页面的 DOM。

下面是一个基本的示例,演示了如何使用 React.createElement() 创建一个简单的元素树:

在 JSX 中,编写如下代码:

1
2
3
4
5
6
const complexElement = (
<div className="container">
<h1 style={{ color: 'yellowgreen' }}>这是标题</h1>
<p>这是段落文本。</p>
</div>
);

通过编译器(如 Babel),上面的 JSX 实际上会被转换为之前展示的 React.createElement() 调用。这就是为什么即使你在使用 JSX 语法时,React 依然需要在作用域内,因为 JSX 实际上就是 React.createElement 方法的语法糖。编译后结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"use strict";

var complexElement = React.createElement(
"div",
{ className: "container" },
React.createElement(
"h1",
{ style: { color: 'yellowgreen' } },
"\u8FD9\u662F\u6807\u9898"
),
React.createElement(
"p",
null,
"\u8FD9\u662F\u6BB5\u843D\u6587\u672C\u3002"
)
);

createElement 函数通常接受三个或更多参数:

  1. type:一个字符串(如 'div''span')、一个 React 类组件,或一个 React 函数组件。这个参数指定了要创建的元素的类型。
  2. props:一个对象,包含了该元素的属性(props)。这些属性后续会传递给组件,一般包括事件处理器、子组件以及其他自定义属性。对于原生 HTML 元素,这些属性对应于实际的 DOM 属性和事件监听器。
  3. children:剩余的参数被认为是子元素。如果有多个子元素,createElement 可以接受多个子元素参数。子元素可以是字符串、数字、React 元素,或者这些类型的数组。

React.createElement() 方法执行后返回一个对象,这个对象通常被称为“React元素”或“虚拟DOM元素”。这个对象是一个轻量级的描述真实DOM结构的JavaScript对象。

下面我们手动实现一个简单的createElement方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
export function createElement(type, props, ...children) {
// 定义初始化的虚拟dom
let vNode = {
$$typeof: Symbol.for('react.element'), // react元素的唯一标识
type, // 标签类型
props: {
children: []
}, // 属性
key: null, // 节点的唯一标识
ref: null // 节点的引用
};
let length = children.length;
vNode.type = type;
// 判断props是否存在
if (props !== null) {
vNode.props = {
...props
};
}
if (length === 1) vNode.props.children = children[0];
if (length > 1) vNode.props.children = children;
return vNode;
}

测试createElement方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { createElement } from './jsxHandler';;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<>
{/* new Array(5)创建的数组为稀疏数组,没有map方法,需要使用fill方法填充为密集数组后才能使用map方法 */}
{new Array(5).fill(null).map((_,k) => {
return <div key={k}>hello world</div>;
})}
</>
);

console.log(createElement(
"h1",
{ style: { color: 'yellowgreen' } },
"\u8FD9\u662F\u6807\u9898")
);

// { 输出结果
// "type": "h1",
// "props": {
// "style": {
// "color": "yellowgreen"
// },
// "children": "这是标题"
// },
// "key": null,
// "ref": null
// }