03.jsx语法

1. JSX基础知识

大小驼峰:PasalCase、CamelCase、kabab-case、xxx_xxx

JSX(javascript and xml\html): 把JS与HTML标签混合在了一起(不是字符串拼接,❌element = “

123
“)

🎈Tips: vscode如何支持JSX语法(格式化、快捷提示…)

  • 后缀名设置为jsx
  • webpack打包的规则中,也是会对.jsx这种文件,按照JS的方式进行处理的

1.1.语法特性

JSX语法具备过滤效果(过滤非法内容),有效防止XSS攻击(扩展思考:总结常见的XSS攻击和预防方案?)

1.1.1.一个根容器

最外层只能一个根容器(一个app有一个容器)

在ReactDOM.createRoot()的时候,不能直接把HTML/BODY做为根容器,需要指定一个额外的盒子「例如:#root」

1.1.2.一个根元素节点

容器可以有多个视图、每一个视图,只有一个“根节点”。

  • 出现多个根节点则报错 Adjacent JSX elements must be wrapped in an enclosing tag.
  • React.Fragment 空文档标记标签:<></>(既保证只有一个根节点,又不新增一个HTML层级结构!!)

1.1.3 动态绑定数据{}

JS表达式嵌入到HTML中,需要使用{} (❗大括号中存放的是JS表达式)

JS表达式:执行有结果的表达式,如:数值、数学运算、三元运算符、循环(借助数组迭代的办法map,foreach没有返回值所以不行)

if(){}不行,JSX中进行的判断一般都要基于三元运算符来完成

命令式循环forfor/infor/ofwhile不行

1.1.4 JS表达式要求

{}语法中嵌入不同的值,所呈现出来的特点

  1. number/string:值是啥,就渲染出来啥

  2. boolean/null/undefined/Symbol/BigInt:渲染的内容是空

  3. 除数组对象外,其余对象一般都不支持在{}中进行渲染,但是也有特殊情况:

    • JSX虚拟DOM对象
    • 给元素设置style行内样式,要求必须写成一个对象格式
  4. 数组对象:把数组的每一项都分别拿出来渲染「并不是变为字符串渲染,中间没有逗号」

    JSX中遍历数组中的每一项,动态绑定多个JSX元素,一般都是基于数组中的map来实现的

    和vue一样,循环绑定的元素要设置key值(作用:用于DOM-DIFF差异化对比)

  5. 函数对象:不支持在{}中渲染,但是可以作为函数组件,用方式渲染。

1.1.5 给元素设置样式

  1. 行内样式:需要基于对象的格式处理,直接写样式字符串会报错;
1
2
3
4
<h2 style={{
color: 'red',
fontSize: '18px' //样式属性要基于驼峰命名法处理
}}>
  1. 设置样式类名:需要把class替换为className;
1
<h2 className="box">

1.2 核心语法初识

为了可以看懂代码于是这里先讲讲一些语法核心。

1
2
3
4
5
6
7
8
9
10
import React from 'react'; //React语法核心
import ReactDOM from 'react-dom/client'; //构建HTML(WebApp)的核心

//获取页面中#root的容器,作为“根”容器
const root = ReactDOM.createRoot(document.getElementById('root'));

//基于render方法渲染我们编写的视图,把渲染后的内容,全部插入到#root中进行渲染
root.render(
....
);

这样就能看懂练手了。😊

1.2 练手

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
//src\index.jsx(index.js改过来的)
// 项目经过瘦身,保留文中内容就行
import React from 'react';
import ReactDOM from 'react-dom/client';

const root = ReactDOM.createRoot(document.getElementById('root'));

/* 需求一:基于数据的值,来判断元素的显示隐藏 */
let flag = false,isRun = false;

//渲染根函数,注意区分两种写法的区别。
root.render(
<>
{/!* 控制元素的display样式:不论显示还是隐藏,元素本身都渲染出来了 *!/}
<button style={{
display: flag ? 'block' : 'none'
}}>按钮1</button>

<br />

{/!* 控制元素渲染或者不渲染 *!/}
{flag ? <button>按钮2</button> : null}

<br />

<button>{isRun ? '正在处理中...' : '立即提交注册'}</button>
</>
);


/* 需求二:从服务器获取了一组列表数据,循环动态绑定相关的内容 */
let data = [{
id: 1,
title: 'zono1'
}, {
id: 2,
title: 'zono2'
}, {
id: 3,
title: 'zono3'
}];//虚拟数据,假装从后端拿来

root.render(
<>
<h2 className="title">今日新闻</h2>
<ul className="news-box">
{data.map((item, index) => {
/* 循环创建的元素一定设置key属性,属性值是本次循环中的“唯一值”「优化DOM-DIFF」 */
return <li key={item.id}>
<em>{index + 1}</em>
&nbsp;&nbsp;
<span>{item.title}</span>
</li>;
})}
</ul>

<br />

{/* 扩展需求:没有数组,就是想单独循环五次 */}
{new Array(5).fill(null).map((_, index) => {
return <button key={index}>
按钮{index + 1}
</button>;
})}
</>
);

2. JSX底层渲染机制

使用babel转换工具,可以直观看见。

第一步:JSX→virtualDOM

运行时会把我们编写的JSX语法编译为虚拟DOM对象(virtualDOM)。

虚拟DOM对象:框架自己内部构建的一套对象体系(对象的相关成员都是React内部规定的),基于这些属性描述出我们所构建视图中的DOM节点的相关特征。

具体流程

https://babeljs.io/repl可以在线观看jsx转换为对象的效果

下面是我编写的例子

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//jsx
<>
<h2 className="title" style={styObj}>123</h2>
<div className ="box">
123
<span>321</span>
<span>zono</span>
</div>
</>


//js
//Classic模式,babel官网的另一个模式不是很懂
//TODO 了解模式
React.createElement(
React.Fragment,
null,
React.createElement(
"h2",
{className: "title",
style: styObj,},
"123"
),
React.createElement(
"div",
{className: "box",},
"123",
React.createElement("span", null, "321"),
React.createElement("span", null, "zono")
)
);

//Automatic模式(目前不做解释)
import { jsx as _jsx } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime";
import { Fragment as _Fragment } from "react/jsx-runtime";
_jsxs(_Fragment, {
children: [
_jsx("h2", {
className: "title",
style: styObj,
children: "123",
}) ,
_jsxs("div", {
className: "box",
children: [
"123" ,
_jsx("span", {
children: "321",
}),
_jsx("span", {
children: "zono",
}),
],
}),
],
});

让我们来解读一下:

  1. 基于babel-preset-react-app(一个专门对react的babel) 把JSX编译为React.createElement(...) 格式。元素节点都会基于createElement进行处理!

出现的函数的解释:

createElement – React 中文文档 (docschina.org)

React.createElement(ele,props,...children)

  • ele:元素标签名\组件名

  • props:元素的属性集合(对象)(无属性,则此值为null)

  • children:当前元素的子节点,第三个及以后的参数都是

  • 返回虚拟DOM,可以看下文虚拟DOM结构

  1. 感觉Automatic模式就是表示的更清楚。
  2. 注意该方法开发时并不常用,有jsx就够了
  1. 再把 createElement 方法执行,创建出virtualDOM虚拟DOM对象(JSX元素、JSX对象、ReactChild对象…)!!

    我们可以把React.createElement打印出来看看效果。

1
2
3
4
5
6
7
8
9
10
11
12
// 大概结构 
virtualDOM = {
$$typeof: Symbol(react.element),//组件类型,官网组件https://react.docschina.org/reference/react-dom/components
ref: null,
key: null,
type: 标签名「或组件」, //h1、h2...
// 存储了元素的相关属性 && 子节点信息
props: {
元素的相关属性,如:className、style,
children:子节点信息(没有子节点则没有这个属性、属性值可能是一个值、也可能是一个数组)
}//必定存在,至少是个空对象
}

实操(非必要)

这里将简单编写一个creatElement函数

感觉太硬了,所以分个p,见04.jsx底层实操

1
2
3
4
5
6
7
8
9
10
11
12
13
//下面是官方笔记,不确定能不能跑通
const createElement = function createElement(type, props, ...children) {
// virtual:[ˈvɜːtʃuəl]
let len = children.length;
let virtualDOM = {
type,
props: {}
};
if (props !== null) virtualDOM.props = { ...props };
if (len === 1) virtualDOM.props.children = children[0];
if (len > 1) virtualDOM.props.children = children;
return virtualDOM;
};

第二步:virtualDOM→真实DOM

真实DOM:浏览器页面中,最后渲染出来,让用户看见的DOM元素(js基础内容)

​ 这一步是基于ReactDOM中的render方法处理的

v16(16版本)写法

1
2
3
4
ReactDOM.render(
<>...</>,
document.getElementById('root')
);

v18

1
2
3
4
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<>...</>
);

实操(非必要)

render

推荐看04

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
29
30
31
32
33
34
35
const render = function render(virtualDOM, container) {
let { type, props } = virtualDOM;
if (typeof type === "string") {
let element = document.createElement(type);
for (let key in props) {
if (!props.hasOwnProperty(key)) break;
if (key === 'className') {
element.setAttribute('class', props[key]);
continue;
}
if (key === 'style') {
let styOBJ = props['style'];
for (let attr in styOBJ) {
if (!styOBJ.hasOwnProperty(attr)) break;
element.style[attr] = styOBJ[attr];
}
continue;
}
if (key === 'children') {
let children = props['children'];
if (!Array.isArray(children)) children = [children];
children.forEach(item => {
if (typeof item === "string") {
element.appendChild(document.createTextNode(item));
return;
}
render(item, element);
});
continue;
}
element.setAttribute(key, props[key]);
}
container.appendChild(element);
}
};

更新:新老virtual对比,然后更新对应内容

第一次渲染页面是直接从virtualDOM->真实DOM;

后期视图更新时,会经过一个DOM-DIFF的对比,计算出补丁包PATCH(两次视图差异的部分),把PATCH补丁包进行渲染!!