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中进行的判断一般都要基于三元运算符
来完成
命令式循环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 给元素设置样式
- 行内样式:需要基于对象的格式处理,直接写样式字符串会报错;
1 2 3 4
| <h2 style={{ color: 'red', fontSize: '18px' }}>
|
- 设置样式类名:需要把class替换为className;
1.2 核心语法初识
为了可以看懂代码于是这里先讲讲一些语法核心。
1 2 3 4 5 6 7 8 9 10
| import React from 'react'; import ReactDOM from 'react-dom/client';
const root = ReactDOM.createRoot(document.getElementById('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
|
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节点的相关特征。
具体流程
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
| <> <h2 className="title" style={styObj}>123</h2> <div className ="box"> 123 <span>321</span> <span>zono</span> </div> </>
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") ) );
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)
- 感觉Automatic模式就是表示的更清楚。
- 注意该方法开发时并不常用,有jsx就够了
再把 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), ref: null, key: null, type: 标签名「或组件」, 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) { 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补丁包进行渲染!!