React18 (二) React元素 和 JSX
1.React元素
1.1 React.createElement()
React.createElement(type, [props], [...children]) - 用来创建React元素,React元素无法修改
1.2 ReactDOM.createRoot()
createRoot(container[, options]) - 用来创建React的根容器,容器用来放置React元素
1.3 root.render()
root.render(element)
当首次调用时,容器节点里的所有 DOM 元素都会被替换,后续的调用则会使用 React 的 DOM 差分算法(DOM diffing algorithm)进行高效的更新。
不会修改容器节点(只会修改容器的子节点)。可以在不覆盖现有子节点的情况下,将组件插入已有的 DOM 节点中。
演示代码
需要下载和导入依赖 https://unpkg.com/react@18.0.0/umd/react.development.js,https://unpkg.com/react-dom@18.0.0/umd/react-dom.development.js
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>React01</title> <script src="js/react.development.js"></script> <script src="js/react-dom.development.js"></script> </head> <body> <div id="root1"></div> <div id="root2"></div> <button id="trigger">Show Button</button> <script> //create <div> by DOM const div = document.createElement('div'); console.log(div); div.innerText = "this is a <div>"; const root1 = document.getElementById("root1"); root1.appendChild(div); // create <div> by React // params: 1.element name 2.element attribute 3.the child element of element const div2 = React.createElement("div", {}, "this is a <div> create by React"); console.log(div2); // create Root element const root2 = ReactDOM.createRoot(document.getElementById("root2")); // render div to root element root2.render(div2); /** update DOM by React, replace all element when need update */ const btn = document.getElementById("trigger"); btn.addEventListener('click', () => { const btn = React.createElement("button", { id: "btn2", className: "btn2", onClick: () => { confirm("go ahead!!!") } }, "New Button"); const div2 = React.createElement("div", {}, "this is a <div> create by React",btn); // render div to root element root2.render(div2); }); </script> </body> </html>
2.JSX
JSX是JavaScript 的语法扩展,JSX 使得我们可以以类似于HTML的形式去使用JS。JSX便是React中声明式编程的体现方式。声明式编程,简单理解就是以结果为导向的编程。使用JSX将我们所期望的网页结构编写出来,然后React再根据JSX自动生成JS代码。所以我们所编写的JSX代码,最终都会转换为以调用React.createElement()创建元素的代码。由于JSX最终需要转换为JS代码执行,所以浏览器并不能正常识别JSX,所以当我们在浏览器中直接使用JSX时,还必须引入babel来完成对代码的编译。
演示代码
需要进入babel依赖,https://unpkg.com/babel-standalone@6/babel.min.js
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>React01 JSX</title> <script src="js/react.development.js"></script> <script src="js/react-dom.development.js"></script> <script src="js/babel.min.js"></script> </head> <body> <div id="root1"></div> <script type="text/babel"> const title = "use babel"; const content = "just demo jsx, and need import bable, setup script type='text/bable'"; const fn = () => "call funtion and get the return value"; const div = <div className="jsx"> <header style={{ fontSize: '20px' }}>{title}</header> <p>{content}</p> <textarea>{fn()}</textarea> </div>; const root = ReactDOM.createRoot(document.getElementById("root1")); root.render(div); </script> </body> </html>
JSX的注意事项:
1.html标签不要加引号
2.有且只有一个根标签
3.html标签小写开头,React组件大写开头
4.可以使用{}插入JS表达式。(表达式:有返回值的语句。JSX也是表达式)
5.属性正常写(class使用className,style必须用{})
6.标签必须正常闭合
7.布尔类型、Null 以及 Undefined 将会忽略
代码地址: https://github.com/showkawa/react18-ZeroToOne/blob/main/react01/jsx.html
3. 虚拟DOM
当我们通过React操作DOM时,比如通过React.createElement()创建元素时。我们所创建的元素并不是真正的DOM对象而是React元素,React元素是React应用的最小组成部分。
与浏览器的 DOM 元素不同,React 元素就是一个普通的JS对象,且创建的开销极小。
React元素不是DOM对象,那为什么可以被添加到页面中去呢?实际上每个React元素都会有一个对应的DOM元素,对React元素的所有操作,最终都会转换为对DOM元素操作,
也就是所谓的虚拟DOM。要理解虚拟DOM,我们需要先了解它的作用。虚拟DOM就好像我们和真实DOM之间的一个桥梁。有了虚拟DOM,使得我们无需去操作真实的DOM元素,
只需要对React元素进行操作,所有操作最终都会映射到真实的DOM元素上。
这不是有点多余吗?直接操作DOM不好吗?为什么要多此一举呢?原因其实很多,这里简单举几个出来。
首先,虚拟DOM简化了DOM操作。凡是用过DOM的都知道Web API到底有多复杂,各种方法,各种属性,数不胜数。查询的、修改的、删除的、添加的等等等等。
然而在虚拟DOM将所有的操作都简化为了一种,那就是创建!React元素是不可变对象,一旦创建就不可更改。要修改元素的唯一方式就是创建一个新的元素去替换旧的元素,
看起来虽然简单粗暴,实则却是简化了DOM的操作。
其次,解决DOM的兼容性问题。DOM的兼容性是一个历史悠久的问题,如果使用原生DOM,总有一些API会遇到兼容性的问题。使用虚拟DOM就完美的避开了这些问题,
所有的操作都是在虚拟DOM上进行的,而虚拟DOM是没有兼容问题的,至于原生DOM是否兼容就不需要我们操心了,全都交给React吧!
最后,我们手动操作DOM时,由于无法完全掌握全局DOM情况,经常会出现不必要的DOM操作,比如,本来只需要修改一个子节点,但却不小心修改了父节点,
导致所有的子节点都被修改。效果呈现上可能没有什么问题,但是性能上确实千差万别,修改一个节点和修改多个节点对于系统的消耗可是完全不同的。
然而在虚拟DOM中,引入了diff算法,React元素在更新时会通过diff算法和之前的元素进行比较,然后只会对DOM做必要的更新来呈现结果。简单来说,
就是拿新建的元素和旧的元素进行比较,只对发生变化的部分对DOM进行更新,减少DOM的操作,从而提升了性能。
4.渲染列表
演示代码
前面的代码演示没有UI效果,如果不想在demo代码操心UI可以直接引入 bootstrap css库,https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>React01 DOM</title> <link href="css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"> <script src="js/react.development.js"></script> <script src="js/react-dom.development.js"></script> <script src="js/babel.min.js"></script> </head> <body> <button id="btn" type="button" class="btn btn-primary">click me</button> <div id="root"></div> <script type="text/babel"> const root = ReactDOM.createRoot(document.getElementById("root")); document.querySelector("#btn").onclick = function () { const data = ["Apache", "Gateway", "ELFK", "Redis", "Kafka"]; const list = <ul>{data.map((item, index) => <li key={item}>{index+1} - {item}</li>)}</ul>; root.render(list); }; </script> </body> </html>
上面的需要渲染出来的<li>标签都有设置属性key,在同一级列表中元素都没有key的话,当你打开控制台时你一定会看到一行红色的内容,它大概内容是:Warning: Each child in a list should have a unique “key” prop.
Warning表示一个警告,警告表示它并不是一个特别严重的错误,但你最好把它处理了,所以当你在React项目中看到这个玩意的时候一定一定要想办法去除掉它。
为什么会报出这个警告呢?事情要往前回溯一下,我们已经知道React是通过虚拟DOM来操作元素的,而React为了提升操作的性能,在DOM发生变化时只会去修改那些发生了变化的元素,这样就大大的减少了DOM操作从而提升了页面渲染的速度。
React怎么知道哪些元素发生变化呢?其实也不难,React每次渲染都会生成一个由React元素构成的树(当然这棵树也对应着一课DOM元素构成的树,但是这里不太重要),React每次重新渲染都会生成一个新的React元素树,在页面刷新前,React会通过内部的diff算法对两个树中的React元素进行比较,并且找到那些发生变化的元素,并将他们的变化在真实的DOM元素上体现出来。
添加了key属性以后警告就消失了,设置key还有一些要求:
1.key必须在当前列表的元素中是唯一的
2.一个元素的key最好是固定的
如果我使用了元素的索引(index)作为key来使用,但这有什么用吗?没用!因为index是根据元素位置的改变而改变的,当我们在前边插入一个新元素时,所有元素的顺序都会一起改变,那么它和React中按顺序比较有什么区别吗?没有区别!而且还麻烦了,唯一的作用就是去除了警告。所以我们开发的时候偶尔也会使用索引作为key,但前提是元素的顺序不会发生变化,除此之外不要用索引做key。