【React系列】JSX核心语法和原理
本文来自#React系列教程:https://mp.weixin.qq.com/mp/appmsgalbum?__biz=Mzg5MDAzNzkwNA==&action=getalbum&album_id=1566025152667107329)
一. ES6 的 class
虽然目前React开发模式中更加流行hooks,但是依然有很多的项目依然是使用类组件(包括AntDesign库中);
但是有很多的同学对ES6中的类不太熟悉,所以这里我还是补充一下;
1.1. 类的定义
在ES6之前,我们通过function
来定义类,但是这种模式一直被很多从其他编程语言(比如Java、C++、OC等等)转到JavaScript的人所不适应。
原因是,大多数面向对象的语言,都是使用class
关键字来定义类的。
而JavaScript也从ES6开始引入了class
关键字,用于定义一个类。
ES6之前定义一个Person
类:
function Person(name, age) {this.name = name;this.age = age;
}Person.prototype.running = function() {console.log(this.name + this.age + "running");
}var p = new Person("why", 18);
p.running();
转换成ES6中的类如何定义呢?
- 类中有一个
constructor
构造方法,当我们通过new
关键字调用时,就会默认执行这个构造方法。构造方法中可以给当前对象添加属性。 - 类中也可以定义其他方法,这些方法会被放到
Person
类的prototype
上。
class Person {constructor(name, age) {this.name = name;this.age = age;}running() {console.log(this.name + this.age + "running");}
}const p = new Person("why", 18);
p.running();
另外,属性也可以直接定义在类中:
class Person {height = 1.88;address = "北京市";constructor(name, age) {this.name = name;this.age = age;}studying() {console.log(this.name + this.age + "studying");}
}
height
和address
是直接定义在类中
1.2. 类的继承
继承是面向对象的一大特性,可以减少我们重复代码的编写,方便公共内容的抽取(也是很多面向对象语言中,多态的前提)。
ES6中增加了extends
关键字来作为类的继承。
我们先写两个类没有继承的情况下,它们存在的重复代码:
Person
类和Student
类
class Person {constructor(name, age) {this.name = name;this.age = age;}running() {console.log(this.name, this.age, "running");}
}class Student {constructor(name, age, sno, score) {this.name = name;this.age = age;this.sno = sno;this.score = score;}running() {console.log(this.name, this.age, "running");}studying() {console.log(this.name, this.age, this.sno, this.score, "studing");}
}
我们可以使用继承来简化代码:
class Student1 extends Person {constructor(name, age, sno, score) {super(name, age);this.sno = sno;this.score = score;}studying() {console.log(this.name, this.age, this.sno, this.score, "studing");}
}const stu1 = new Student1("why", 18, 110, 100);
stu1.studying();
注意:在constructor
中,子类必须通过super
来调用父类的构造方法,对父类进行初始化,否则会报错。
二. 案例练习
2.1. 列表展示
真实开发中,我们的数据通常会从服务器获取,比较常见的是获取一个列表数据,保存到一个数组中进行展示。
比如现在有一个电影列表,我们如何通过React进行展示呢?
我们还是通过一个组件来完成:
class App extends React.Component {constructor(props) {super(props);this.state = {movies: ["星际穿越", "大话西游", "盗梦空间", "少年派"]}}render() {// var movieLis = [];// for (var i in this.state.movies) {// movieLis.push((<li>{this.state.movies[i]}</li>));// }return (<div><h2>电影列表</h2><ul>{this.state.movies.map((item, index) => {return (<li>{item}</li>)})}</ul></div>)}
}ReactDOM.render(<App/>, document.getElementById("app"));
2.2. 计数器案例
电影列表的案例中并没有交互,我们再来实现一个计数器的案例:
class App extends React.Component {constructor(props) {super(props);this.state = {counter: 0}}render() {return (<div><h2>当前计数:{this.state.counter}</h2><button onClick={this.increment.bind(this)}>+1</button><button onClick={this.decrement.bind(this)}>-1</button></div>)}increment() {this.setState({counter: this.state.counter+1})}decrement() {this.setState({counter: this.state.counter-1})}
}ReactDOM.render(<App/>, document.getElementById("app"));
三. JSX语法解析
3.1. 认识JSX的语法
我们先来看一段代码:
<script type="text/babel">const element = <h2>Hello World</h2>ReactDOM.render(element, document.getElementById("app"));
</script>
这段element
变量的声明右侧赋值的标签语法是什么呢?
- 它不是一段字符串(因为没有使用引号包裹),它看起来是一段HTML原生,但是我们能在
js
中直接给一个变量赋值html
吗? - 其实是不可以的,如果我们将
type="text/babel"
去除掉,那么就会出现语法错误; - 它到底是什么呢?其实它是一段
jsx
的语法;
JSX是什么?
- JSX是一种JavaScript的语法扩展(eXtension),也在很多地方称之为 JavaScript XML,因为看起就是一段XML语法;
- 它用于描述我们的UI界面,并且其完全可以和JavaScript融合在一起使用;
- 它不同于Vue中的模块语法,你不需要专门学习模块语法中的一些指令(比如v-for、v-if、v-else、v-bind);
为什么React选择了JSX?
- React认为渲染逻辑本质上与其他UI逻辑存在内在耦合
- 比如UI需要绑定事件(button、a原生等等);
- 比如UI中需要展示数据状态,在某些状态发生改变时,又需要改变UI;
- 他们之间是密不可分,所以React没有将标记分离到不同的文件中,而是将它们组合到了一起,这个地方就是组件(Component);
- 在这里,我们只需要知道,JSX其实是嵌入到JavaScript中的一种结构语法;
JSX的书写规范:
- JSX的顶层只能有一个根元素,所以我们很多时候会在外层包裹一个
div
原生(或者使用后面我们学习的Fragment
); - 为了方便阅读,我们通常在jsx的外层包裹一个小括号
()
,这样可以方便阅读,并且jsx
可以进行换行书写; - JSX中的标签可以是单标签,也可以是双标签;注意:如果是单标签,必须以
/>
结尾;
JSX的本质,我们后面再来讨论;
3.2. JSX嵌入表达式
如果我们jsx中的内容是动态的,我们可以通过表达式来获取:
- 书写规则:
{表达式}
- 大括号内可以是变量、字符串、数组、函数调用等任意js表达式;
3.2.1. jsx中的注释
jsx是嵌入到JavaScript中的一种语法,所以在编写注释时,需要通过JSX的语法来编写:
<div>{/* 我是一段注释 */}<h2>Hello World</h2>
</div>
3.2.2. JSX嵌入变量
- 情况一:当变量是
Number、String、Array
类型时,可以直接显示 - 情况二:当变量
是null、undefined、Boolean
类型时,内容为空;- 如果希望可以显示
null、undefined、Boolean
,那么需要转成字符串; - 转换的方式有很多,比如
toString
方法、和空字符串拼接,String(变量)
等方式;
- 如果希望可以显示
- 情况三:对象类型不能作为子元素(not valid as a React child)
class App extends React.Component {constructor(props) {super(props);this.state = {name: "why",age: 18,hobbies: ["篮球", "唱跳", "rap"],test1: null,test2: undefined,flag: false,friend: {name: "kobe",age: 40}}}render() {return (<div>{/* 我是一段注释 */}<h2>Hello World</h2></div><div>{/* 1.可以直接显示 */}<h2>{this.state.name}</h2><h2>{this.state.age}</h2><h2>{this.state.hobbies}</h2>{/* 2.不显示 */}<h2>{this.state.test1}</h2><h2>{this.state.test1 + ""}</h2><h2>{this.state.test2}</h2><h2>{this.state.test2 + ""}</h2><h2>{this.state.flag}</h2><h2>{this.state.flag + ""}</h2>{/* 3.不显示 */}<h2>123{this.state.friend}</h2></div>)}
}ReactDOM.render(<App/>, document.getElementById("app"));
补充:为什么null、undefined、Boolean
在JSX中要显示为空内容呢?
原因是在开发中,我们会进行很多的判断;
- 在判断结果为
false
时,不显示一个内容; - 在判断结果为
true
时,显示一个内容;
这个时候,我们可以编写如下代码:
class App extends React.Component {constructor(props) {super(props);this.state = {flag: false}}render() {return (<div>{this.state.flag ? <h2>我是标题</h2> : null}{this.state.flag && <h2>我是标题</h2>}</div>)}
}
3.3.3. JSX嵌入表达式
JSX中,也可以是一个表达式。
这里我们演练三个,其他的大家在开发中灵活运用:
- 运算表达式
- 三元运算符
- 执行一个函数
class App extends React.Component {constructor(props) {super(props);this.state = {firstName: "kobe",lastName: "bryant",age: 20}}render() {return (<div>{/* 运算表达式 */}<h2>{this.state.firstName + " " + this.state.lastName}</h2>{/* 三元运算符 */}<h2>{this.state.age >= 18 ? "成年人": "未成年人"}</h2>{/* 执行一个函数 */}<h2>{this.sayHello("kobe")}</h2></div>)}sayHello(name) {return "Hello " + name;}
}
3.3.4. jsx绑定属性
很多时候,描述的HTML原生会有一些属性,而我们希望这些属性也是动态的:
- 比如元素都会有
title
属性 - 比如
img
元素会有src
属性 - 比如
a
元素会有href
属性 - 比如元素可能需要绑定
class
- 注意:绑定
class
比较特殊,因为class
在js
中是一个关键字,所以jsx
中不允许直接写class
- 写法:使用
className
替代
- 注意:绑定
- 比如原生使用内联样式
style
style
后面跟的是一个对象类型,对象中是样式的属性名和属性值;- 注意:这里会将属性名转成驼峰标识,而不是连接符
-
;
我们来演示一下属性的绑定:
class App extends React.Component {constructor(props) {super(props);this.state = {title: "你好啊",imgUrl: "https://upload.jianshu.io/users/upload_avatars/1102036/c3628b478f06.jpeg?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240",link: "https://www.baidu.com",active: false}}render() {return (<div><h2 title={this.state.title}>Hello World</h2><img src={this.state.imgUrl} alt=""/><a href={this.state.link} target="_blank">百度一下</a><div className={"message " + (this.state.active ? "active": "")}>你好啊</div><div className={["message", (this.state.active ? "active": "")].join(" ")}>你好啊</div><div style={{fontSize: "30px", color: "red", backgroundColor: "blue"}}>我是文本</div></div>)}
}
3.3. jsx事件监听
3.3.1. 和原生绑定区别
如果原生DOM原生有一个监听事件,我们可以如何操作呢?
- 方式一:获取DOM原生,添加监听事件;
- 方式二:在HTML原生中,直接绑定
onclick
;
我们这里演练一下方式二:
<button onclick="btnClick()">点我一下</button>
<script>function btnClick() {console.log("按钮发生了点击");}
</script>
btnClick()
这样写的原因是onclick
绑定的后面是跟上JavaScript代码;
在React中是如何操作呢?
我们来实现一下React中的事件监听,这里主要有两点不同
- React 事件的命名采用小驼峰式(camelCase),而不是纯小写;
- 我们需要通过
{}
传入一个事件处理函数,这个函数会在事件发生时被执行;
class App extends React.Component {render() {return (<div><button onClick={this.btnClick}>点我一下(React)</button></div>)}btnClick() {console.log("React按钮点击了一下")}
}
3.3.2. this绑定问题
在事件执行后,我们可能需要获取当前类的对象中相关的属性:
class App extends React.Component {constructor(props) {super(props);this.state = {message: "你好啊,李银河"}}render() {return (<div><button onClick={this.btnClick}>点我一下(React)</button></div>)}btnClick() {console.log(this);console.log(this.state.message);}
}
比如我们这里打印:this.state.message
,但是这里会报错:Cannot read property 'state' of undefined
原因是this
在这里是undefined
,如果我们这里直接打印this
,也会发现它是一个undefined
。
为什么是undefined
呢?
- 原因是
btnClick
函数并不是我们主动调用的,而且当button
发生改变时,React内部调用了btnClick
函数; - 而它内部调用时,并不知道要如何绑定正确的
this
;
如何解决this
的问题呢?
方案一:bind
给btnClick
显示绑定this
在传入函数时,我们可以主动绑定this
:
<button onClick={this.btnClick.bind(this)}>点我一下(React)</button>
- 这里我们主动将
btnClick
中的this
通过bind
来进行绑定(显示绑定) - 那么之后
React
内部调用btnClick
函数时,就会有一个this
,并且是我们绑定的this
;
但是呢,如果我有两个函数都需要用到btnClick
的绑定:
<button onClick={this.btnClick.bind(this)}>点我一下(React)</button>
<button onClick={this.btnClick.bind(this)}>也点我一下(React)</button>
- 我们发现
bind(this)
需要书写两遍;
这个我们可以通过在构造方法中直接给this.btnClick
绑定this
来解决:
class App extends React.Component {constructor(props) {super(props);this.state = {message: "你好啊,李银河"}this.btnClick = this.btnClick.bind(this);}render() {return (<div><button onClick={this.btnClick}>点我一下(React)</button><button onClick={this.btnClick}>也点我一下(React)</button></div>)}btnClick() {console.log(this);console.log(this.state.message);}
}
- 注意查看
constructor
中我们的操作:this.btnClick = this.btnClick.bind(this);
方案二:使用 ES6 class fields 语法
你会发现我这里将btnClick
的定义变成了一种赋值语句:
class App extends React.Component {constructor(props) {super(props);this.state = {message: "你好啊,李银河"}}render() {return (<div><button onClick={this.btnClick}>点我一下(React)</button><button onClick={this.btnClick}>也点我一下(React)</button></div>)}btnClick = () => {console.log(this);console.log(this.state.message);}
}
- 这是ES6中给类定义属性的方法,称之为
class fields
语法; - 因为这里我们赋值时,使用了箭头函数,所以在当前函数中的
this
会去上一个作用域中查找; - 而上一个作用域中的
this
就是当前的对象;
方案三:事件监听时传入箭头函数(推荐)
因为 onClick
中要求我们传入一个函数,那么我们可以直接定义一个箭头函数传入:
class App extends React.Component {constructor(props) {super(props);this.state = {message: "你好啊,李银河"}}render() {return (<div><button onClick={() => this.btnClick()}>点我一下(React)</button><button onClick={() => this.btnClick()}>也点我一下(React)</button></div>)}btnClick() {console.log(this);console.log(this.state.message);}
}
- 传入的箭头函数的函数体是我们需要执行的代码,我们直接执行
this.btnClick()
; this.btnClick()
中通过this
来指定会进行隐式绑定,最终this
也是正确的;
3.3.3. 事件参数传递
在执行事件函数时,有可能我们需要获取一些参数信息:比如event
对象、其他参数
情况一:获取event
对象
- 很多时候我们需要拿到
event
对象来做一些事情(比如阻止默认行为) - 假如我们用不到
this
,那么直接传入函数就可以获取到event
对象;
class App extends React.Component {constructor(props) {render() {return (<div><a href="http://www.baidu.com" onClick={this.btnClick}>点我一下</a></div>)}btnClick(e) {e.preventDefault();console.log(e);}
}
情况二:获取更多参数
- 有更多参数时,我们最好的方式就是传入一个箭头函数,主动执行的事件函数,并且传入相关的其他参数;
class App extends React.Component {constructor(props) {super(props);this.state = {names: ["衣服", "鞋子", "裤子"]}}render() {return (<div><a href="http://www.baidu.com" onClick={this.aClick}>点我一下</a>{this.state.names.map((item, index) => {return (<a href="#" onClick={e => this.aClick(e, item, index)}>{item}</a>)})}</div>)}aClick(e, item, index) {e.preventDefault();console.log(item, index);console.log(e);}
}
四. 条件渲染
某些情况下,界面的内容会根据不同的情况显示不同的内容,或者决定是否渲染某部分内容:
- 在vue中,我们会通过指令来控制:比如v-if、v-show;
- 在React中,所有的条件判断都和普通的JavaScript代码一致;
常见的条件渲染的方式有哪些呢?
4.1. 条件判断语句
一种方式是当逻辑较多时,通过条件判断:
class App extends React.Component {constructor(props) {super(props);this.state = {isLogin: true}}render() {let titleJsx = null;if (this.state.isLogin) {titleJsx = <h2>欢迎回来~</h2>} else {titleJsx = <h2>请先登录~</h2>}return (<div>{titleJsx}</div>)}
}
当然,我们也可以将其封装到一个独立的函数中:
class App extends React.Component {constructor(props) {super(props);this.state = {isLogin: true}}render() {return (<div>{this.getTitleJsx()}</div>)}getTitleJsx() {let titleJsx = null;if (this.state.isLogin) {titleJsx = <h2>欢迎回来~</h2>} else {titleJsx = <h2>请先登录~</h2>}return titleJsx;}
}
4.2. 三元运算符
另外一种实现条件渲染的方法就是三元运算符:condition ? true : false
;
三元运算符适用于没有太多逻辑的代码:只是根据不同的条件直接返回不同的结果
class App extends React.Component {constructor(props) {super(props);this.state = {isLogin: true}}render() {return (<div><h2>{this.state.isLogin ? "欢迎回来~": "请先登录~"}</h2><button onClick={e => this.loginBtnClick()}>{this.state.isLogin ? "退出": "登录"}</button></div>)}loginBtnClick() {this.setState({isLogin: !this.state.isLogin})}
}
4.3. 与运算符&&
在某些情况下,我们会遇到这样的场景:
- 如果条件成立,渲染某一个组件;
- 如果条件不成立,什么内容也不渲染;
如果我们使用三元运算符,是如何做呢?
{this.state.isLogin ? <h2>{this.state.username}</h2> : null}
其实我们可以通过逻辑与&&
来简化操作:
{this.state.isLogin && <h2>{this.state.username}</h2>}
4.4. v-show效果
针对一个HTML原生,渲染和不渲染之间,如果切换的非常频繁,那么会相对比较损耗性能:
- 在开发中,其实我们可以通过
display
的属性来控制它的显示和隐藏; - 在控制方式在vue中有一个专门的指令:v-show;
- React没有指令,但是React会更加灵活(灵活带来的代价就是需要自己去实现);
我来看一下如何实现:
render() {const { isLogin, username } = this.state;const nameDisplay = isLogin ? "block": "none";return (<div><h2 style={{display: nameDisplay}}>{username}</h2><button onClick={e => this.loginBtnClick()}>{isLogin ? "退出": "登录"}</button></div>)}
五. jsx列表渲染
5.1. 列表渲染
真实开发中我们会从服务器请求到大量的数据,数据会以列表的形式存储:
- 比如歌曲、歌手、排行榜列表的数据;
- 比如商品、购物车、评论列表的数据;
- 比如好友消息、动态、联系人列表的数据;
在React中并没有像Vue模块语法中的v-for指令,而且需要我们通过JavaScript代码的方式组织数据,转成JSX:
- 很多从Vue转型到React的同学非常不习惯,认为Vue的方式更加的简洁明了;
- 但是React中的JSX正是因为和JavaScript无缝的衔接,让它可以更加的灵活;
- 另外我经常会提到React是真正可以提高我们编写代码能力的一种方式;
如何展示列表呢?
- 在React中,展示列表最多的方式就是使用数组的
map
高阶函数;
数组的map
函数语法如下:
callback
:生成新数组元素的函数,使用三个参数:currentValue
,callback 数组中正在处理的当前元素。index
可选,callback 数组中正在处理的当前元素的索引。array
可选,map 方法调用的数组。
thisArg
可选:执行callback
函数时值被用作this
。
var new_array = arr.map(function callback(currentValue, [index, [array]]) {// Return element for new_array
}[, thisArg])
我们来演练一下之前的案例:
class App extends React.Component {constructor(props) {super(props);this.state = {movies: ["盗梦空间", "大话西游", "流浪地球", "少年派", "食神", "美人鱼", "海王"]}}render() {return (<div><h2>电影列表</h2><ul>{this.state.movies.map(item => {return <li>{item}</li>})}</ul></div>)}
}ReactDOM.render(<App/>, document.getElementById("app"));
5.2. 数组处理
很多时候我们在展示一个数组中的数据之前,需要先对它进行一些处理:
- 比如过滤掉一些内容:
filter
函数 - 比如截取数组中的一部分内容:
slice
函数
比如我当前有一个数组中存放了一系列的数字:[10, 30, 120, 453, 55, 78, 111, 222]
案例需求:获取所有大于50的数字,并且展示前3个数组
class App extends React.Component {constructor(props) {super(props);this.state = {numbers: [10, 30, 120, 453, 55, 78, 111, 222]}}render() {return (<div><h2>数字列表</h2><ul>{this.state.numbers.filter(item => item >= 50).slice(0, 3).map(item => {return <li>{item}</li>})}</ul></div>)}
}ReactDOM.render(<App/>, document.getElementById("app"));
基本上列表渲染都是使用数组的高阶函数map
、filter
等来处理
5.3. 列表的key
我们会发现在前面的代码中只要展示列表都会报一个警告:
这个警告是告诉我们需要在列表展示的jsx中添加一个key
。
至于如何添加一个key
,为什么要添加一个key
,这个我们放到后面讲解setState
时再来讨论;
六. JSX原理解析
6.1. JSX转换本质
实际上,jsx
仅仅只是 React.createElement(component, props, ...children)
函数的语法糖。
所有的jsx
最终都会被转换成React.createElement
的函数调用。
React.createElement
在源码的什么位置呢?
createElement
需要传递三个参数:
- 参数一:
type
- 当前
ReactElement
的类型; - 如果是标签元素,那么就使用字符串表示
“div”
; - 如果是组件元素,那么就直接使用组件的名称;
- 当前
- 参数二:
config
- 所有jsx中的属性都在
config
中以对象的属性和值的形式存储
- 所有jsx中的属性都在
- 参数三:
children
- 存放在标签中的内容,以
children
数组的方式进行存储; - 当然,如果是多个元素呢?React内部有对它们进行处理,处理的源码在下方
- 存放在标签中的内容,以
对children
进行的处理:
- 从第二个参数开始,将其他所有的参数,放到
props
对象的children
中
const childrenLength = arguments.length - 2;
if (childrenLength === 1) {props.children = children;
} else if (childrenLength > 1) {const childArray = Array(childrenLength);for (let i = 0; i < childrenLength; i++) {childArray[i] = arguments[i + 2];}if (__DEV__) {if (Object.freeze) {Object.freeze(childArray);}}props.children = childArray;
}
真实的转换过程到底长什么样子呢?我们可以从多个角度来查看。
6.1.1. Babel官网查看
我们知道默认jsx
是通过babel
帮我们进行语法转换的,所以我们之前写的jsx
代码都需要依赖babel
。
可以在babel
的官网中快速查看转换的过程:https://babeljs.io/repl/#?presets=react
在这里我们编写一些jsx
代码,来查看运行后的结果:
<div className="app"><div className="header"><h1 title="标题">我是网站标题</h1></div><div className="content"><h2>我是h2元素</h2><button onClick={e => console.log("+1")}>+1</button><button onClick={e => console.log("+1")}>-1</button></div><div className="footer"><p>我是网站的尾部</p></div>
</div>
6.1.2. 编写createElement
还有一种办法是我们自己来编写React.createElement
代码:
class App extends React.Component {constructor(props) {render() {/*#__PURE__*/const result = React.createElement("div", {className: "app"}, /*#__PURE__*/React.createElement("div", {className: "header"}, /*#__PURE__*/React.createElement("h1", {title: "\u6807\u9898"}, "\u6211\u662F\u7F51\u7AD9\u6807\u9898")), /*#__PURE__*/React.createElement("div", {className: "content"}, /*#__PURE__*/React.createElement("h2", null, "\u6211\u662Fh2\u5143\u7D20"), /*#__PURE__*/React.createElement("button", {onClick: e => console.log("+1")}, "+1"), /*#__PURE__*/React.createElement("button", {onClick: e => console.log("+1")}, "-1")), /*#__PURE__*/React.createElement("div", {className: "footer"}, /*#__PURE__*/React.createElement("p", null, "\u6211\u662F\u7F51\u7AD9\u7684\u5C3E\u90E8")));return result;}
}ReactDOM.render(React.createElement(App, null) , document.getElementById("app"));
上面的整个代码,我们就没有通过jsx
来书写了,界面依然是可以正常的渲染。
另外,在这样的情况下,你还需要babel
相关的内容吗?不需要了
- 所以,
type="text/babel"
可以被我们删除掉了; - 所以,
<script src="../react/babel.min.js"></script>
可以被我们删除掉了;
6.2. 虚拟DOM
6.2.1. 虚拟DOM的创建过程
我们通过 React.createElement
最终创建出来一个 ReactElement
对象:
return ReactElement(type,key,ref,self,source,ReactCurrentOwner.current,props,
);
这个ReactElement
对象是什么作用呢?React为什么要创建它呢?
- 原因是React利用
ReactElement
对象组成了一个JavaScript
的对象树; JavaScript
的对象树就是大名鼎鼎的虚拟DOM(Virtual DOM);
如何查看ReactElement
的树结构呢?
- 我们可以将之前的
jsx
返回结果进行打印; - 注意下面代码中我打
jsx
的打印;
render() {const jsx = (<div className="app"><div className="header"><h1 title="标题">我是网站标题</h1></div><div className="content"><h2>我是h2元素</h2><button onClick={e => console.log("+1")}>+1</button><button onClick={e => console.log("+1")}>-1</button></div><div className="footer"><p>我是网站的尾部</p></div></div>)console.log(jsx);return jsx;
}
打印结果,在浏览器中查看:
而ReactElement
最终形成的树结构就是 Virtual DOM
;
整体的转换过程如下:
6.2.2. 为什么采用虚拟DOM
为什么要采用虚拟DOM,而不是直接修改真实的DOM呢?
- 很难跟踪状态发生的改变:原有的开发模式,我们很难跟踪到状态发生的改变,不方便针对我们应用程序进行调试;
- 操作真实DOM性能较低:传统的开发模式会进行频繁的DOM操作,而这一的做法性能非常的低;
DOM操作性能非常低:
首先,document.createElement
本身创建出来的就是一个非常复杂的对象;
- https://developer.mozilla.org/zh-CN/docs/Web/API/Document/createElement
其次,DOM操作会引起浏览器的回流和重绘,所以在开发中应该避免频繁的DOM操作;
这里我们举一个例子:
比如我们有一组数组需要渲染:[0, 1, 2, 3, 4],我们会怎么做呢?
<ul><li>0</li><li>1</li><li>2</li><li>3</li><li>4</li>
</ul>
后来,我们又增加了5条数据:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
for (var i=5; i<10; i++) {var li = document.createElement("li");li.innerHTML = arr[i];ul.appendChild(li);
}
上面这段代码的性能怎么样呢?非常低效
- 因为我们通过
document.createElement
创建元素,再通过ul.appendChild(li)
渲染到DOM上,进行了多次DOM操作; - 对于批量操作的,最好的办法不是一次次修改DOM,而是对批量的操作进行合并;(比如可以通过
DocumentFragment
进行合并);
虚拟DOM帮助我们从命令式编程转到了声明式编程的模式
React官方的说法:Virtual DOM 是一种编程理念。
在这个理念中,UI以一种理想化或者说虚拟化的方式保存在内存中,并且它是一个相对简单的JavaScript对象,我们可以通过ReactDOM.render
让 虚拟DOM 和 真实DOM同步起来,这个过程中叫做协调(Reconciliation);
这种编程的方式赋予了React声明式的API:你只需要告诉React希望让UI是什么状态,React来确保DOM和这些状态是匹配的。
你不需要直接进行DOM操作,就可以从手动更改DOM、属性操作、事件处理中解放出来。
七. 书籍购物车案例练习
参考:https://mp.weixin.qq.com/s?__biz=Mzg5MDAzNzkwNA==&mid=2247483948&idx=1&sn=0d788ea90f500fd1dc3a0813cfb9da2b&chksm=cfe3f1d3f89478c585413ecd8b50cba4d98e5893ddb150dd21f686947832505337c092a5ba58&scene=178&cur_album_id=1566025152667107329#rd
相关文章:

【React系列】JSX核心语法和原理
本文来自#React系列教程:https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzg5MDAzNzkwNA&actiongetalbum&album_id1566025152667107329) 一. ES6 的 class 虽然目前React开发模式中更加流行hooks,但是依然有很多的项目依然是使用类组件&#x…...

【C++初阶(九)】C++模版(初阶)----函数模版与类模版
本专栏内容为:C学习专栏,分为初阶和进阶两部分。 通过本专栏的深入学习,你可以了解并掌握C。 💓博主csdn个人主页:小小unicorn ⏩专栏分类:C 🚚代码仓库:小小unicorn的代码仓库&…...
Permission denied
Permission denied:权限被拒绝,没有访问文件的权限。 查询对文件的权限: ls -l 文件名称 r为可读权限,w为可写权限,x为可执行权限。 授权文件rwx,可读可写可执行权限: chmod 777 文件名称 如…...

轻松学会电脑如何录制音频
随手录音,保留证据以便后续出现问题进行判定,或者保存会议音频记录方便后续根据录音内容整理自己会议记录不足之处等等;越来越多的地方需要用到录音,那么在电脑上该如何进行音频录制呢?特别是使用比较广泛的Windows电脑…...

react antd,echarts全景视图
1.公告滚动,40s更新一次 2.echarts图标 左右轮播 60s更新一次 3.table 表格 import { useState, useEffect } from react;import Slider from react-slick; import slick-carousel/slick/slick-theme.css; import slick-carousel/slick/slick.css;import Layout fro…...

GD32 支持IAP的bootloader开发,使用串口通过Ymodem协议传输固件(附代码)
资料下载: https://download.csdn.net/download/wouderw/88714985 一、概述 关于IAP的原理和Ymodem协议,本文不做任何论述,本文只论述bootloader如何使用串口通过Ymodem协议接收升级程序并进行IAP升级,以及bootloader和主程序两个工程的配置…...

【C#】知识点实践序列之UrlEncode在线URL网址编码、解码
欢迎来到《小5讲堂》,大家好,我是全栈小5。 这是2024年第8篇文章,此篇文章是C#知识点实践序列文章, 博主能力有限,理解水平有限,若有不对之处望指正! 地址编码大家应该比较经常遇到和使用到&…...

泽攸科技完全自主研制的电子束光刻机取得阶段性成果
国产电子束光刻机实现自主可控,是实现我国集成电路产业链自主可控的重要一环。近日,泽攸科技联合松山湖材料实验室开展的全自主电子束光刻机整机的开发与产业化项目取得重大进展,成功研制出电子束光刻系统,实现了电子束光刻机整机…...

上篇 | CDP应用篇之兴趣标签的3种破圈玩法
谈到客户洞察,在这个以客户为中心、以数据为驱动的客户经营时代,贯通数据,联动CDP客户数据平台、SCRM、会员、营销一站式的客户洞察解决方案,成为了头部房企们的万千宠爱。其中关于人群兴趣标签的破圈玩法,我们结合过往…...

智能的核心依然是哲学的三个基本问题
智能的发展与哲学的三个基本问题密切相关,作为一个复杂领域,智能涉及到人类认知和行为的模拟与复制,因而也会涉及到哲学的核心问题。 存在论:智能的存在论问题涉及到什么是智能以及智能系统的本质。这包括对于意识、思维和自主性的…...
用python实现提取word中的所有图片
你可以使用python-docx库来处理word文件,然后遍历文件中的所有形状,找到图片。 首先,你需要安装python-docx库。在命令行中输入以下命令进行安装: 复制代码 pip install python-docx 然后,你可以使用以下代码提取wo…...
CoTracker 环境配置与ORB 特征点提取结合实现视频特征点追踪
CoTracker 环境配置&与ORB 特征点提取结合实现视频特征点追踪 文章目录 CoTracker 环境配置&与ORB 特征点提取结合实现视频特征点追踪Step1:配置 CoTracker 环境Step2:运行官方的例程Step3:结合 ORB 特征点提取结果展示: …...

10000000000 大瓜背后的真相(附 PDD 算法真题)
10 个亿的大事? 京东诉阿里强迫商家「二选一」,京东胜诉,获阿里赔偿 10 亿。 很多小伙伴见到公主号开创了锐评时事板块,当天就在后台留言问我看法。 先说结论:这是一则「媒体影响力」远大于「实际意义」的报道。 首先&…...
python爬虫,简单的requests的get请求,百度搜索实例
1、百度搜索实例 import requests url https://www.baidu.com/s? # key_word 迪丽热巴 key_word input(输入搜索内容:) headers {User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537…...
UNION 和 UNION ALL
概述 UNION 和 UNION ALL 都是 SQL 中用于将多个 SELECT 语句的结果合并成一个结果集的操作符。它们都适用于需要将多个表或查询结果合并在一起的情况。但是它们的行为略有不同。 区别 UNION 和 UNION ALL 的区别在于,UNION 会将结果集合并成一个不含重复行的结果…...

NPS 内网穿透安装
NPS 内网穿透安装 NPS 内网穿透安装服务端搭建SSH配置流程 NPS 内网穿透安装 NPS分为服务端和客户端,对应的不同操作系统软件可以在GitHub RELEASES自行选择下载。 服务端搭建 由于个人非企业级使用,为了方便直接使用docker安装 1.docker运行 (注意…...
【C++学习笔记】C++多值返回写法
C不像python可以轻易地处理多值返回问题,处理使用指针或者引用将需要返回的值通过参数带出来,还有几种特殊的方式。 引用自:https://mp.weixin.qq.com/s/VEvUxpcJPsxT9kL7-zLTxg 1. Tuple tie 通过使用std::tie,我们可以将tuple…...
读取带有梯度的张量的具体的值
问题:存在一个带有梯度的张量tensor_example,怎么读取它具体的值 方法:可以使用 .detach().cpu().numpy() 的组合。这样可以在保留值的同时,将张量从计算图中分离(detach)并移动到 CPU 上。 示例…...

【分布式微服务专题】SpringSecurity快速入门
目录 前言阅读对象阅读导航前置知识笔记正文一、Spring Security介绍1.1 什么是Spring Security1.2 它是干什么的1.3 Spring Security和Shiro比较 二、快速开始2.1 用户认证2.1.1 设置用户名2.1.1.1 基于application.yml配置文件2.1.1.2 基于Java Config配置方式 2.1.2 设置加密…...

EasyRecovery2024永久免费版电脑数据恢复软件
EasyRecovery是一款操作安全、价格便宜、用户自主操作的非破坏性的只读应用程序,它不会往源驱上写任何东西,也不会对源驱做任何改变。它支持从各种各样的存储介质恢复删除或者丢失的文件,其支持的媒体介质包括:硬盘驱动器、光驱、…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...

如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...

Python 实现 Web 静态服务器(HTTP 协议)
目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1)下载安装包2)配置环境变量3)安装镜像4)node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1)使用 http-server2)详解 …...

解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用
在工业制造领域,无损检测(NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统,以非接触式光学麦克风技术为核心,打破传统检测瓶颈,为半导体、航空航天、汽车制造等行业提供了高灵敏…...

Axure 下拉框联动
实现选省、选完省之后选对应省份下的市区...

ZYNQ学习记录FPGA(一)ZYNQ简介
一、知识准备 1.一些术语,缩写和概念: 1)ZYNQ全称:ZYNQ7000 All Pgrammable SoC 2)SoC:system on chips(片上系统),对比集成电路的SoB(system on board) 3)ARM:处理器…...