【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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/293014.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

imgaug库指南(三):从入门到精通的【图像增强】之旅

引言 在深度学习和计算机视觉的世界里&#xff0c;数据是模型训练的基石&#xff0c;其质量与数量直接影响着模型的性能。然而&#xff0c;获取大量高质量的标注数据往往需要耗费大量的时间和资源。正因如此&#xff0c;数据增强技术应运而生&#xff0c;成为了解决这一问题的…

14.7-时序反馈移位寄存器建模

时序反馈移位寄存器建模 1&#xff0c;阻塞赋值实现的LFSR&#xff0c;实际上并不具有LFSR功能1.1.1&#xff0c;RTL设计&#xff0c;阻塞赋值1.1.2&#xff0c;tb测试代码1.1.3&#xff0c;波形仿真输出&#xff0c;SIM输出&#xff0c;没实现LFSR1.2.1&#xff0c;RTL设计&am…

【导出与导入Virtualbox虚拟机和启动连接openGauss数据库】

【导出与导入Virtualbox虚拟机和启动连接openGauss数据库】 一、导出虚拟机二、导入虚拟机三、启动数据库四、使用Data Studio连接数据库 一、导出虚拟机 选择关机状态的虚拟机 -> 管理菜单 -> 导出虚拟电脑 点击完成后&#xff0c;需要等待一小段时间&#xff0c;如…

神经网络的核心:帮助新手理解 PyTorch 非线性激活函数

目录 torch.nn子函数非线性激活详解 nn.Softmin Softmin 函数简介 函数工作原理 参数详解 使用技巧与注意事项 示例代码 nn.Softmax Softmax 函数简介 函数工作原理 参数详解 使用技巧与注意事项 示例代码 nn.Softmax2d Softmax2d 函数简介 函数工作原理 输入…

Java异常简单介绍

文章目录 1. 异常分类和关键字1.1 分类1.2 关键字 2. Error2.1 Error定义2.2 常见的Error2.2.1 VirtualMachineError2.2.2 ThreadDeath2.2.3 LinkageError2.2.4 AssertionError2.2.5 InternalError2.2.6 OutOfMemoryError2.2.6.1 OOM原因2.2.6.2 OutOfMemoryError会导致宕机吗 …

Python(wordcloud):根据文本数据(.txt文件)绘制词云图

一、前言 本文将介绍如何利用python来根据文本数据&#xff08;.txt文件&#xff09;绘制词云图&#xff0c;除了绘制常规形状的词云图&#xff08;比如长方形&#xff09;&#xff0c;还可以指定词云图的形状。 二、相关库的介绍 1、安装相关的库 pip install jieba pip i…

c语言-指针进阶

文章目录 前言一、字符指针二、数组指针2.1 数组指针基础2.2 数组指针作函数参数 总结 前言 在c语言基础已经介绍过关于指针的概念和基本使用&#xff0c;本篇文章进一步介绍c语言中关于指针的应用。 一、字符指针 字符指针是指向字符的指针。 结果分析&#xff1a; "ab…

【常用排序算法】快速排序

##快速排序 快速排序由于排序效率在同为O(N*logN)的几种排序方法中效率较高&#xff0c;因此经常被采用&#xff0c;再加上快速排序思想----分治法 先从数列中取出一个数作为基准数pivot。分区过程&#xff0c;将比这个数大的数全放到它的右边&#xff0c;小于或等于它的数全放…

索引类型-哈希索引

一. 前言 前面我们简单介绍了数据库的B-Tree索引&#xff0c;下面我们介绍另一种索引类型-哈希索引。 二. 哈希索引的简介 哈希索引(hash index) 基于哈希表实现&#xff0c;只有精确匹配索引所有列的查询才有效。对于每一行数据&#xff0c;存储引擎都会对所有索引列计算一个…

智能穿戴时代 | 米客方德SD NAND的崭新优势

SD NAND在智能穿戴上的优势 SD NAND是一种可以直接焊接在智能穿戴设备主板上的存储芯片&#xff0c;其小型化设计有助于紧凑设备尺寸&#xff0c;同时提供可靠的嵌入式存储解决方案。 这种集成设计减少了空间占用&#xff0c;同时确保设备在高度活动的环境中更为稳定。SD NAND…

【数据库系统概论】数据库恢复机制

系统文章目录 数据库的四个基本概念&#xff1a;数据、数据库、数据库管理系统和数据库系统 数据库系统的三级模式和二级映射 数据库系统外部的体系结构 数据模型 关系数据库中的关系操作 SQL是什么&#xff1f;它有什么特点&#xff1f; 数据定义之基本表的定义/创建、修改和…

[论文笔记] Megtron_LM 0、报错:vscode调试无法传进去参数 launch.json文件获取args参数

解决方法&#xff1a; 配置好launch.json文件后&#xff0c;应该点运行和调试里面的运行按钮。而不是直接点文件右上角的debug。 可以看到terminal中&#xff0c;如果没有正常加载launch.json&#xff0c;则参数中没有args的参数。 如果正常加载&#xff0c;可以看到args的很多…

Mysql 重要知识点1(含面试题1)

Mysql 知识点较多&#xff0c;这里涵盖了基本知识点&#xff0c;包括SQL语句 、重要面试题等。后面还有几章Mysql的知识点&#xff0c;分别是刷题总结与进阶优化SQL 面试题等。 目录 Mysql 安装Mysql 重要知识点SQL 重要语句面试题精选 Mysql 安装 1.官网下载mysql5.7版本压缩…

@Scheduled定时任务现状与改进

项目场景&#xff1a; 定时任务现状&#xff1a;每个项目都会有一些配置信息&#xff0c;这些信息我们是都放在一个配置服务中&#xff0c;这个服务会定时从配置表中加载所有配置存入本地JVM内存&#xff0c;以供调用方获取&#xff08;调用方集成了配置服务的SDK&#xff0c;…

PyTorch数据并行(DP/DDP)浅析

一直以来都是用的单机单卡训练模型&#xff0c;虽然很多情况下已经足够了&#xff0c;但总有一些情况得上分布式训练&#xff1a; 模型大到一张卡放不下&#xff1b;单张卡batch size不敢设太大&#xff0c;训练速度慢&#xff1b;当你有好几张卡&#xff0c;不想浪费&#xf…

设计模式 七大原则

1.单一职责原则 单一职责原则&#xff08;SRP&#xff1a;Single responsibility principle&#xff09;又称单一功能原则 核心&#xff1a;解耦和增强内聚性&#xff08;高内聚&#xff0c;低耦合&#xff09;。 描述&#xff1a; 类被修改的几率很大&#xff0c;因此应该专注…

Python处理音频

从video中抽取audio from moviepy.editor import VideoFileClip from pydub import AudioSegmentvideo_path /opt/audio/audios/video1.mp4 audio_path /opt/audio/audios/video1.wav # 提取的音频保存路径# 加载视频文件 video VideoFileClip(video_path)# 提取音频 audi…

华为DriveONE电机控制器拆解实拍

如果说之前的问界M5、M7&#xff0c;华为让我们看到其在智能化上确实拥有遥遥领先的能力&#xff0c;那么在智界S7上&#xff0c;则让我们看到华为在动力、底盘这些硬件执行层面&#xff0c;竟然也有不输给很多车企的实力。1、华为电驱&#xff0c;全球第一&#xff1f;在智界S…

如何使用Cloudreve+Cpolar搭建个人PHP云盘系统并发布公网可访问

文章目录 1、前言2、本地网站搭建2.1 环境使用2.2 支持组件选择2.3 网页安装2.4 测试和使用2.5 问题解决 3、本地网页发布3.1 cpolar云端设置3.2 cpolar本地设置 4、公网访问测试5、结语 1、前言 自云存储概念兴起已经有段时间了&#xff0c;各互联网大厂也纷纷加入战局&#…

微信小程序:flex常用布局

在我们平时微信小程序开发过程中为了页面能达到设计小伙伴的预期&#xff0c;追求还原度&#xff0c;那我们肯定会使用很多常用的布局方式&#xff0c;那我们今天就介绍一下微信小程序中常用的一些flex布局 1、常用flex布局 /** 水平垂直居中 **/ .flex-center {display: fle…