1. JSX基础知识
大小驼峰:PasalCase、CamelCase、kabab-case、xxx_xxx
JSX(javascript and xml\html): 把JS与HTML标签混合在了一起(不是字符串拼接,❌element = "<div>123</div>"
)
🎈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中进行的判断一般都要基于三元运算符
来完成命令式循环
for
,for/in
,for/of
、while
不行
1.1.4 JS表达式要求
{}语法中嵌入不同的值,所呈现出来的特点
-
number/string:值是啥,就渲染出来啥
-
boolean/null/undefined/Symbol/BigInt:渲染的内容是空
-
除数组对象外,其余对象一般都不支持在{}中进行渲染,但是也有特殊情况:
- JSX虚拟DOM对象
- 给元素设置style行内样式,要求必须写成一个对象格式
-
数组对象:把数组的每一项都分别拿出来渲染「并不是变为字符串渲染,中间没有逗号」
JSX中遍历数组中的每一项,动态绑定多个JSX元素,一般都是基于数组中的map来实现的
和vue一样,循环绑定的元素要设置key值(作用:用于DOM-DIFF差异化对比)
-
函数对象:不支持在{}中渲染,但是可以作为函数组件,用方式渲染。
1.1.5 给元素设置样式
- 行内样式:需要基于对象的格式处理,直接写样式字符串会报错;
js
<h2 style={{
color: 'red',
fontSize: '18px' //样式属性要基于驼峰命名法处理
}}>
- 设置样式类名:需要把class替换为className;
html
<h2 className="box">
1.2 核心语法初识
为了可以看懂代码于是这里先讲讲一些语法核心。
jsx
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 练手
jsx
//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>
<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节点的相关特征。
具体流程
下面是我编写的例子
js
//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",
}),
],
}),
],
});
让我们来解读一下:
- 基于
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结构
- 感觉Automatic模式就是表示的更清楚。
- 注意该方法开发时并不常用,有jsx就够了
-
再把 createElement 方法执行,创建出virtualDOM虚拟DOM对象(JSX元素、JSX对象、ReactChild对象...)!!
我们可以把
React.createElement
打印出来看看效果。
json
// 大概结构
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底层实操
jsx
//下面是官方笔记,不确定能不能跑通
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版本)写法
jsx
ReactDOM.render(
<>...</>,
document.getElementById('root')
);
v18
jsx
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<>...</>
);
实操(非必要)
render
推荐看
04
javascript
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补丁包进行渲染!!