当前位置: 首页 > news >正文

【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");}
}
  • heightaddress是直接定义在类中

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比较特殊,因为classjs中是一个关键字,所以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的问题呢?

方案一:bindbtnClick显示绑定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"));

基本上列表渲染都是使用数组的高阶函数mapfilter等来处理

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中以对象的属性和值的形式存储
  • 参数三: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的对象树就是大名鼎鼎的虚拟DOMVirtual 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系列教程&#xff1a;https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzg5MDAzNzkwNA&actiongetalbum&album_id1566025152667107329) 一. ES6 的 class 虽然目前React开发模式中更加流行hooks&#xff0c;但是依然有很多的项目依然是使用类组件&#x…...

【C++初阶(九)】C++模版(初阶)----函数模版与类模版

本专栏内容为&#xff1a;C学习专栏&#xff0c;分为初阶和进阶两部分。 通过本专栏的深入学习&#xff0c;你可以了解并掌握C。 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;C &#x1f69a;代码仓库&#xff1a;小小unicorn的代码仓库&…...

Permission denied

Permission denied&#xff1a;权限被拒绝&#xff0c;没有访问文件的权限。 查询对文件的权限&#xff1a; ls -l 文件名称 r为可读权限&#xff0c;w为可写权限&#xff0c;x为可执行权限。 授权文件rwx&#xff0c;可读可写可执行权限&#xff1a; chmod 777 文件名称 如…...

轻松学会电脑如何录制音频

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

react antd,echarts全景视图

1.公告滚动&#xff0c;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协议&#xff0c;本文不做任何论述&#xff0c;本文只论述bootloader如何使用串口通过Ymodem协议接收升级程序并进行IAP升级&#xff0c;以及bootloader和主程序两个工程的配置…...

【C#】知识点实践序列之UrlEncode在线URL网址编码、解码

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

泽攸科技完全自主研制的电子束光刻机取得阶段性成果

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

上篇 | CDP应用篇之兴趣标签的3种破圈玩法

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

智能的核心依然是哲学的三个基本问题

智能的发展与哲学的三个基本问题密切相关&#xff0c;作为一个复杂领域&#xff0c;智能涉及到人类认知和行为的模拟与复制&#xff0c;因而也会涉及到哲学的核心问题。 存在论&#xff1a;智能的存在论问题涉及到什么是智能以及智能系统的本质。这包括对于意识、思维和自主性的…...

用python实现提取word中的所有图片

你可以使用python-docx库来处理word文件&#xff0c;然后遍历文件中的所有形状&#xff0c;找到图片。 首先&#xff0c;你需要安装python-docx库。在命令行中输入以下命令进行安装&#xff1a; 复制代码 pip install python-docx 然后&#xff0c;你可以使用以下代码提取wo…...

CoTracker 环境配置与ORB 特征点提取结合实现视频特征点追踪

CoTracker 环境配置&与ORB 特征点提取结合实现视频特征点追踪 文章目录 CoTracker 环境配置&与ORB 特征点提取结合实现视频特征点追踪Step1&#xff1a;配置 CoTracker 环境Step2&#xff1a;运行官方的例程Step3&#xff1a;结合 ORB 特征点提取结果展示&#xff1a; …...

10000000000 大瓜背后的真相(附 PDD 算法真题)

10 个亿的大事&#xff1f; 京东诉阿里强迫商家「二选一」&#xff0c;京东胜诉&#xff0c;获阿里赔偿 10 亿。 很多小伙伴见到公主号开创了锐评时事板块&#xff0c;当天就在后台留言问我看法。 先说结论&#xff1a;这是一则「媒体影响力」远大于「实际意义」的报道。 首先&…...

python爬虫,简单的requests的get请求,百度搜索实例

1、百度搜索实例 import requests url https://www.baidu.com/s? # key_word 迪丽热巴 key_word input(输入搜索内容&#xff1a;) 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 的区别在于&#xff0c;UNION 会将结果集合并成一个不含重复行的结果…...

NPS 内网穿透安装

NPS 内网穿透安装 NPS 内网穿透安装服务端搭建SSH配置流程 NPS 内网穿透安装 NPS分为服务端和客户端&#xff0c;对应的不同操作系统软件可以在GitHub RELEASES自行选择下载。 服务端搭建 由于个人非企业级使用&#xff0c;为了方便直接使用docker安装 1.docker运行 (注意…...

【C++学习笔记】C++多值返回写法

C不像python可以轻易地处理多值返回问题&#xff0c;处理使用指针或者引用将需要返回的值通过参数带出来&#xff0c;还有几种特殊的方式。 引用自&#xff1a;https://mp.weixin.qq.com/s/VEvUxpcJPsxT9kL7-zLTxg 1. Tuple tie 通过使用std::tie&#xff0c;我们可以将tuple…...

读取带有梯度的张量的具体的值

问题&#xff1a;存在一个带有梯度的张量tensor_example&#xff0c;怎么读取它具体的值 方法&#xff1a;可以使用 .detach().cpu().numpy() 的组合。这样可以在保留值的同时&#xff0c;将张量从计算图中分离&#xff08;detach&#xff09;并移动到 CPU 上。 示例&#xf…...

【分布式微服务专题】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是一款操作安全、价格便宜、用户自主操作的非破坏性的只读应用程序&#xff0c;它不会往源驱上写任何东西&#xff0c;也不会对源驱做任何改变。它支持从各种各样的存储介质恢复删除或者丢失的文件&#xff0c;其支持的媒体介质包括&#xff1a;硬盘驱动器、光驱、…...

iphone 苹果 IOS 越狱详细图文保姆级教程非常简单

现在随着各个工具的升级&#xff0c;越狱的难度也是越来越低&#xff0c;还记得 iphone 4 的时候我越狱还是花钱请别人搞得&#xff0c;现在只要你的机型支持越狱&#xff0c;下个工具点一点就可以了&#xff0c;非常简单 目前来说整个越狱过程中&#xff0c;寻找合适机型是最…...

华为HarmonyOS 创建第一个鸿蒙应用 运行Hello World

使用DevEco Studio创建第一个项目 Hello World 1.创建项目 创建第一个项目&#xff0c;命名为HelloWorld&#xff0c;点击Finish 选择Empty Ability模板&#xff0c;点击Next Hello World 项目已经成功创建&#xff0c;接来下看看效果 2.预览 Hello World 点击右侧的预…...

[C#]Onnxruntime部署Chinese CLIP实现以文搜图以文找图功能

【官方框架地址】 https://github.com/OFA-Sys/Chinese-CLIP 【算法介绍】 在当今的大数据时代&#xff0c;文本信息处理已经成为了计算机科学领域的核心议题之一。为了高效地处理海量的文本数据&#xff0c;自然语言处理&#xff08;NLP&#xff09;技术应运而生。而在诸多N…...

openssl ans1定义的实体

由于openssl中的ASN1的结构是通过宏来定义的&#xff0c;导致我们经常找不到他的结构在哪里&#xff0c;通过阅读rfc&#xff0c;并且对照OPENSSL&#xff0c;发现OPENSSL中的结构基本是按照相关rfc中的名称&#xff0c;在openssl中进行搜索&#xff0c;就能找到具体的定义了。…...

【Linux Shell】4. 数组

文章目录 【 1. 数组的定义 】【 2. 读取数组 】【 3. 关联数组 】3.1 关联数组的定义3.2 关联数组元素的调用 【 4. 获取数组中的所有元素 】【 5. 获取数组的长度 】 数组中可以存放多个值。 Bash Shell 只支持一维数组&#xff08;不支持多维数组&#xff09;&#xff0c;初…...

蓝牙运动耳机哪款好用?运动用什么耳机比较好?2024运动耳机推荐

​在众多的耳机类型中&#xff0c;运动耳机因其独特的设计和功能而备受青睐。它们不仅要具备出色的音质&#xff0c;还需要能够适应激烈的运动环境&#xff0c;如防水、防汗、牢固耐用等。今天&#xff0c;我想向大家推荐一些在这些方面表现出色的运动耳机&#xff0c;这些耳机…...

XD6500S一款串口SiP模块 射频LoRa芯片 内置sx1262

1.1产品介绍 XD6500S是一款集射频前端和LoRa射频于一体的LoRa SIP模块系列收发器SX1262 senies&#xff0c;支持LoRa⑧和FSK调制。LoRa技术是一种扩频协议优化低数据速率&#xff0c;超长距离和超低功耗用于LPWAN应用的通信。 XD6500S设计具有4.2 mA的有效接收电流消耗&#…...

【华为OD机试真题2023CD卷 JAVAJS】测试用例执行计划

华为OD2023(C&D卷)机试题库全覆盖,刷题指南点这里 测试用例执行计划 时间限制:1s 空间限制:256MB 限定语言:不限 题目描述: 某个产品当前迭代周期内有N个特性()需要进行覆盖测试,每个特性都被评估了对应的优先级,特性使用其ID作为下标进行标识。 设计了M个测试用…...

猫长期吃猫粮好吗?主食冻干猫粮那种好吃又健康

许多铲屎官可能认为&#xff0c;只需给猫咪喂食猫粮就足够了。然而&#xff0c;猫咪实际上是肉食动物&#xff0c;对蛋白质的需求非常高。冻干猫粮采用低温真空干燥处理技术&#xff0c;将鲜肉经过预冻、升华、解析三个过程&#xff0c;去除水分的同时保持蛋白质等营养物质不变…...

计算机毕业设计-----ssm停车位租赁系统

项目介绍 该系统采用了经典的springmvc&#xff0c;spring&#xff0c;mybatis的框架组合&#xff0c;对于物业公司来说&#xff0c;有助于管理车位信息。系统分为了两个角色&#xff1a;车主和租客。 车主主要功能包括&#xff1a; 停车位信息 停车位列表 添加停车位 租赁合…...

mac怎么将字体导入wordpress/佛山百度网站快速排名

背景介绍-升级JRE后Applet无法运行 这两天Oracle发布了JDK的最新版本 JFK_1.8_60。我们有个客户第一时间更新了最新的版本&#xff0c;这导致了他无法运行我们的Applet(SwingJavaFX2)产品。 每次遇到因为升级JRE而引起的环境问题&#xff0c;心中都要默默骂Oracle 100遍。以前…...

如何做外卖网站app/seo成功的案例和分析

线性表是 具有相同特性的数据元素的有限序列n≥0 线性表的逻辑特性 1、 线性表的顺序存储 问题1&#xff1a;存储空间分配不灵活问题2&#xff1a;运算的空间复杂度高 2、线性表的链式存储 抽象数据类型&#xff1a; 从具体应用中抽象出共性的逻辑结构和基本操作&#xff…...

深圳网站设计公司招聘/福州seo兼职

将这3块磁盘升级为动态磁盘。&#xff08;截图&#xff09;&#xff08;1&#xff09;将添加的3块磁盘右键初始化磁盘&#xff08;2&#xff09;右键磁盘转换到动态磁盘2.在磁盘1上创建一个简单卷&#xff0c;大小为10M&#xff0c;格式化为NTFS&#xff0c;卷标为E&#xff1a…...

网站流量站怎么做的/谷歌seo服务公司

LAMP 系统性能调优之内核调优措施 2011-03-18 11:21 Sean A. Walberg 网络转载 字号&#xff1a;T | T在对系统的 Apache、PHP 和 MySQL 组件进行调优之前&#xff0c;应该花一些时间确保底层 Linux 组件的运行正常。这点是非常重要的&#xff01; AD&#xff1a;2014WOT全球软…...

建设个人网站需要多少钱/成都专业的整站优化

以下常用的SQL语句有利于我们分析数据库的基本信息&#xff0c;然后根据查询的结果进行优化。 1. 查看索引碎片 无论何时对基础数据执行插入、更新或删除操作&#xff0c;SQL Server 数据库引擎都会自动维护索引。随着时间的推移&#xff0c;这些修改可能会导致索引中的信息分散…...

医院网站推广渠道/设计案例网

0 ps -ef | grep 端口号/进程名/执行文件展示结果:上述命令解释如下:UID PIC PPID C STIME TTY TIME CMD# UID 谁操作发起的这个进程# PIC 这个程序的 ID 下面的 PPID 则是父程序的 ID&#xff1b;# C CPU 使用的资源百分比# CMD 所下达的指令因此CMD为对应命令案例2&#x…...