React初体验及Jsx语法

77 阅读14分钟

一、React初体验

React是什么呢?

  • 相信每个做开发的人对它都或多或少有一些印象;
  • 这里我们来看一下官方对它的解释:用于构建用户界面的 JavaScript 库;

目前对于前端开发来说,几乎很少直接使用原生的JavaScript来开发应用程序,而是选择一个JavaScript库 (框架)。

  • 在过去的很长时间内,jQuery是被使用最多的JavaScript库;
  • 在过去的一份调查中显示,全球前10,000个访问最高的网站中,有65%使用了jQuery,是当时最受欢迎的JavaScript库;
  • 但是,目前甚至已经处于淘汰的边缘了;

◼ **而无论是国内外,最流行的其实是三大框架:Vue、React、Angular。

如何学习React

image-20221017140802294

React的介绍(技术角度)

React是什么?

image-20221017141152196

React特点

声明式编程:

  • 声明式编程是目前整个大前端开发的模式:Vue、React、Flutter、SwiftUI;
  • 它允许我们只需要维护自己的状态,当状态改变时,React可以根据最新的状态去渲染我们的UI界面;

组件化开发:

  • 组件化开发页面目前前端的流行趋势,我们会将复杂的界面拆分成一个个小的组件;
  • 如何合理的进行组件的划分和设计也是后面我会讲到的一个重点;

多平台适配:

  • 2013年,React发布之初主要是开发Web页面;
  • 2015年,Facebook推出了ReactNative,用于开发移动端跨平台;(虽然目前Flutter非常火爆,但是还是有很多公司在使用ReactNative);
  • 2017年,Facebook推出ReactVR,用于开发虚拟现实Web应用程序;(VR也会是一个火爆的应用场景);

React的开发依赖

开发React必须依赖三个库:

  • react:包含react所必须的核心代码
  • react-dom:react渲染在不同平台所需要的核心代码
  • babel:将jsx转换成React代码的工具

第一次接触React会被它繁琐的依赖搞蒙,居然依赖这么多东西: (直接放弃?)

  • 对于Vue来说,我们只是依赖一个vue.js文件即可,但是react居然要依赖三个包。
  • 其实呢,这三个库是各司其职的,目的就是让每一个库只单纯做自己的事情;
  • 在React的0.14版本之前是没有react-dom这个概念的,所有功能都包含在react里;

为什么要进行拆分呢?原因就是react-native。

  • react包中包含了react web和react-native所共同拥有的核心代码。

  • react-dom针对web和native所完成的事情不同:

    • web端:react-dom会将jsx最终渲染成真实的DOM,显示在浏览器中
    • native端:react-dom会将jsx最终渲染成原生的控件(比如Android中的Button,iOS中的UIButton)。

Babel和React的关系

babel是什么呢?

  • Babel ,又名 Babel.js
  • 是目前前端使用非常广泛的编译器、转移器。
  • 比如当下很多浏览器并不支持ES6的语法,但是确实ES6的语法非常的简洁和方便,我们开发时希望使用它。
  • 那么编写源码时我们就可以使用ES6来编写,之后通过Babel工具,将ES6转成大多数浏览器都支持的ES5的语法。

React和Babel的关系:

  • 默认情况下开发React其实可以不使用babel。
  • 但是前提是我们自己使用 React.createElement 来编写源代码,它编写的代码非常的繁琐和可读性差。
  • 那么我们就可以直接编写jsx(JavaScript XML)的语法,并且让babel帮助我们转换成React.createElement。

React的依赖引入

所以,我们在编写React代码时,这三个依赖都是必不可少的。

那么,如何添加这三个依赖:

  • 方式一:直接CDN引入
  • 方式二:下载后,添加本地依赖
  • 方式三:通过npm管理(后续脚手架再使用)

暂时我们直接通过CDN引入,来演练下面的示例程序:

    <!-- 添加依赖 -->
    <script
      crossorigin
      src="https://unpkg.com/react@18/umd/react.development.js"
    ></script>
    <script
      crossorigin
      src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
    ></script>
    <!-- bable -->
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

Hello World

第一步:在界面上通过React显示一个Hello World

  • 注意:这里我们编写React的script代码中,必须添加 type="text/babel",作用是可以让babel解析jsx的语法

ReactDOM. createRoot函数:用于创建一个React根,之后渲染的内容会包含在这个根中

  • 参数:将渲染的内容,挂载到哪一个HTML元素上

    • 这里我们已经提定义一个id为app的div

root.render函数:

  • 参数:要渲染的根组件

我们可以通过{}语法来引入外部的变量或者表达式

<!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>Hello React</title>
  </head>
  <body>
    <div id="root"></div>
​
    <!-- 添加依赖 -->
    <script
      crossorigin
      src="https://unpkg.com/react@18/umd/react.development.js"
    ></script>
    <script
      crossorigin
      src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
    ></script>
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
​
    <script type="text/babel">
      // 编写React代码(jsx语法)
      // jsx语法 -> 普通的JavaScript代码 -> bable
    
      const root = ReactDOM.createRoot(document.querySelector("#root"));
​
      // 1.将文本定义成变量
      let message = "hello world";
​
      // 2.监听按钮的点击
      function bntClick() {
        // 1.修改数据
        message = "Hello React";
​
        // 2.重新渲染界面
        rootRender();
      }
​
      rootRender();
      // 3.封装一个渲染函数
      function rootRender() {
        root.render(
          <div>
            <h2>{message}</h2>
            <button onClick={bntClick}>修改文本</button>
          </div>
        );
      }
    </script>
  </body>
</html>

Hello React – 组件化开发

整个逻辑其实可以看做一个整体,那么我们就可以将其封装成一个组件:

  • 我们说过root.render 参数是一个HTML元素或者一个组件;
  • 所以我们可以先将之前的业务逻辑封装到一个组件中,然后传入到 ReactDOM.render 函数中的第一个参数;

在React中,如何封装一个组件呢? 这里我们暂时使用类的方式封装组件:

  1. 定义一个类(类名大写,组件的名称是必须大写的,小写会被认为是HTML元素),继承自React.Component

  2. 实现当前组件的render函数

    • render当中返回的jsx内容,就是之后React会帮助我们渲染的内容
组件化 - 数据依赖

组件化问题一:* 数据在哪里定义***?**

在组件中的数据,我们可以分成两类:

  • 参与界面更新的数据:当数据变量时,需要更新组件渲染的内容;
  • 不参与界面更新的数据:当数据变量时,不需要更新将组建渲染的内容;

参与界面更新的数据我们也可以称之为是参与数据流,这个数据是定义在当前对象的state中

  • 我们可以通过在构造函数中 this.state = {定义的数据}

  • 当我们的数据发生变化时,我们可以调用 this.setState 来更新数据,并且通知React进行update操作;

    • 在进行update操作时,就会重新调用render函数,并且使用最新的数据,来渲染界面
      class App extends React.Component {
        // 组件数据
        constructor() {
          super();
​
          this.state = {
            message: "Hello World",
          };
          // 对需要绑定的方法,提前绑定好this
          this.bntClick = this.bntClick.bind(this);
        }
    }
组件化 – 事件绑定

组件化问题二:事件绑定中的this

  • 在类中直接定义一个函数,并且将这个函数绑定到元素的onClick事件上,当前这个函数的this指向的是谁呢?

默认情况下是undefined

  • 很奇怪,居然是undefined;
  • 因为在正常的DOM操作中,监听点击,监听函数中的this其实是节点对象(比如说是button对象);
  • 这次因为React并不是直接渲染成真实的DOM,我们所编写的button只是一个语法糖,它的本质React的Element对象;
  • 那么在这里发生监听的时候,react在执行函数时并没有绑定this,默认情况下就是一个undefined;

我们在绑定的函数中,可能想要使用当前对象,比如执行 this.setState 函数,就必须拿到当前对象的this

  • 我们就需要在传入函数时,给这个函数直接绑定this

  • 类似于下面的写法:

    <button onClick={this.changeText.bind(this)}>改变文本</button>
    

image-20221011165220049

重构Hello World
<!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>Document</title>
  </head>
  <body>
    <div id="root"></div>
​
    <script src="../lib/react.js"></script>
    <script src="../lib//react-dom.js"></script>
    <script src="../lib//bable.js"></script>
​
    <script type="text/babel">
      // 使用组件进行重构代码
      // 类组件和函数式组件
      class App extends React.Component {
        // 组件数据
        constructor() {
          super();
​
          this.state = {
            message: "Hello World",
          };
          // 对需要绑定的方法,提前绑定好this
          this.bntClick = this.bntClick.bind(this);
        }
        // 组件方法(实例方法)
        bntClick() {
          // console.log(this);   undefined
          // 内部完成了两件事:1.将state中的message值修改掉 2.自动执行重新执行render函数
          this.setState({
            message: "Hello React",
          });
        }
​
        // 渲染内容render方法
        render() {
          return (
            <div>
              <h2>{this.state.message}</h2>
              <button onClick={this.bntClick}>修改文本</button>
            </div>
          );
        }
      }
​
      // 将组件渲染到界面上
      const root = ReactDOM.createRoot(document.querySelector("#root"));
      // App根组件
      root.render(<App />);
    </script>
  </body>
</html>

综合案例

电影列表展示

<!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>Document</title>
  </head>
  <body>
    <div id="root"></div>
​
    <script src="../lib/react.js"></script>
    <script src="../lib//react-dom.js"></script>
    <script src="../lib//bable.js"></script>
​
    <script type="text/babel">
      // 1.创建root
      const root = ReactDOM.createRoot(document.querySelector("#root"));
​
      // 封装App组件
      class App extends React.Component {
        constructor() {
          super();
​
          this.state = {
            movies: ["星际穿越", "流浪地球", "大话西游"],
          };
        }
​
        render() {
          // 1.对movies进行for循环
          // const liEls = [];
          // for (let i = 0; i < this.state.movies.length; i++) {
          //   const movie = this.state.movies[i];
          //   const liEl = <li>{movie}</li>;
          //   liEls.push(liEl);
          // }
​
          // 2. movies数组 =》 liEls数组
          // const liEls = this.state.movies.map((movie) => <li>{movie}</li>);
          return (
            <div>
              <h2>电影列表</h2>
              <ul>
                {this.state.movies.map((movie) => (
                  <li>{movie}</li>
                ))}
              </ul>
            </div>
          );
        }
      }
​
      // 2.渲染组件
      root.render(<App />);
    </script>
  </body>
</html>

计数器案例

<!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>Document</title>
  </head>
  <body>
    <div id="root"></div>
​
    <script src="../lib/react.js"></script>
    <script src="../lib/react-dom.js"></script>
    <script src="../lib/bable.js"></script>
​
    <script type="text/babel">
      const root = ReactDOM.createRoot(document.querySelector("#root"));
      class App extends React.Component {
        constructor() {
          super();
          this.state = {
            number: 100,
          };
        }
​
        render() {
          const { number } = this.state;
          return (
            <div>
              <h2>当前计数:{number}</h2>
              <button onClick={this.increment.bind(this)}>+1</button>
              <button onClick={this.decrement.bind(this)}>-1</button>
            </div>
          );
        }
​
        // 组件的方法
        increment() {
          this.setState({
            number: this.state.number + 1,
          });
        }
        decrement() {
          this.setState({
            number: this.state.number - 1,
          });
        }
      }
​
      root.render(<App />);
    </script>
  </body>
</html>

二、JSX语法

认识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的使用

jsx中的注释

{/* jsx的注释写法 */}

JSX嵌入变量作为子元素

  • 情况一:当变量是Number、String、Array类型时,可以直接显示

  • 情况二:当变量是null、undefined、Boolean类型时,内容为空;

    • 如果希望可以显示null、undefined、Boolean,那么需要转成字符串;
    • 转换的方式有很多,比如toString方法、和空字符串拼接,String(变量)等方式;
  • 情况三:Object对象类型不能作为子元素(not valid as a React child)

JSX嵌入表达式

  • 运算表达式
  • 三元运算符
  • 执行一个函数

jsx绑定属性

  • 比如元素都会有title属性
  • 比如img元素会有src属性
  • 比如a元素会有href属性
  • 比如元素可能需要绑定class
  • 比如原生使用内联样式style
    <script type="text/babel">
      // 1.定义根组件
      class App extends React.Component {
        constructor() {
          super();
          this.state = {
            message: "Hello World",
            title: "hhh",
            imgUrl:
              "https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fc90dcae96df46df906098437db738f0~tplv-k3u1fbpfcp-watermark.image?",
            isActive: true,
            ObjectStyle: { color: "red", fontSize: "30px" },
          };
        }
​
        render() {
          const { title, imgUrl, isActive, ObjectStyle } = this.state;
          // 1.class绑定写法一:字符串的拼接
          const className = `aaa bbb ${isActive ? "active" : ""}`;
          // 2.class绑定写法二:将所有的class放到一个数组中
          const classList = ["aaa", "bbb"];
          if (isActive) classList.push("active");
          // 3.class绑定的写法三:第三方库classnames
​
          return (
            <div>
              {/* 1.基本绑定 */}
              <h2 title={title}>我是H2元素</h2>
              <img src={imgUrl} alt="" />
​
              {/* 2.绑定class属性:最好使用className */}
              <h2 className={className}>哈哈哈</h2>
              <h2 className={classList.join(" ")}>哈哈哈哈</h2>
​
              {/* 3.绑定style属性:绑定的是对象类型 */}
              <h2 style={{ color: "red", fontSize: "30px" }}>哈哈哈哈哈哈</h2>
              <h2 style={ObjectStyle}>哈哈哈哈哈哈</h2>
            </div>
          );
        }
      }
      // 2.创建root并且渲染App组件
      const root = ReactDOM.createRoot(document.querySelector("#root"));
      root.render(<App />);
    </script>

React事件绑定

如果原生DOM原生有一个监听事件,我们可以如何操作呢?

  • 方式一:获取DOM原生,添加监听事件;
  • 方式二:在HTML原生中,直接绑定onclick;

在React中是如何操作呢? 我们来实现一下React中的事件监听,这里主要有两点不同

  • React 事件的命名采用小驼峰式(camelCase),而不是纯小写;
  • 我们需要通过{}传入一个事件处理函数,这个函数会在事件发生时被执行;
    <script type="text/babel">
      // 1.定义根组件
      class App extends React.Component {
        constructor() {
          super();
          this.state = {
            count: 100,
          };
        }
​
        bnt1Click() {
          this.setState({
            count: "1000",
          });
        }
        bnt2Click = () => {
          this.setState({
            count: "2000",
          });
        };
        bnt3Click() {
          this.setState({
            count: "9999",
          });
        }
​
        render() {
          const { count } = this.state;
          return (
            <div>
              {/* 1.this绑定方式一:bind绑定 */}
              <button onClick={this.bnt1Click.bind(this)}>按钮</button>
​
              {/* 2.this绑定方式二:ES6 class field */}
              <button onClick={this.bnt2Click}>按钮</button>
​
              {/* 3.this绑定方式三:直接传入一个箭头函数(zyao) */}
              <button onClick={() => console.log("bnt3Click")}>按钮</button>
              <button onClick={() => this.bnt3Click()}>按钮</button>
              <h2>{count}</h2>
            </div>
          );
        }
      }
      // 2.创建root并且渲染App组件
      const root = ReactDOM.createRoot(document.querySelector("#root"));
      root.render(<App />);
    </script>

this的绑定问题

在事件执行后,我们可能需要获取当前类的对象中相关的属性,这个时候需要用到this

  • 如果我们这里直接打印this,也会发现它是一个undefined

为什么是undefined呢?

  • 原因是btnClick函数并不是我们主动调用的,而且当button发生改变时,React内部调用了btnClick函数;
  • 而它内部调用时,并不知道要如何绑定正确的this;

如何解决this的问题呢?

  • 方案一:bind给btnClick显示绑定this
  • 方案二:使用 ES6 class fields 语法
  • 方案三:事件监听时传入箭头函数(个人推荐)
<script type="text/babel">
      // 1.定义根组件
      class App extends React.Component {
        constructor() {
          super();
          this.state = {
            count: 100,
          };
        }
​
        bnt1Click() {
          this.setState({
            count: "1000",
          });
        }
        bnt2Click = () => {
          this.setState({
            count: "2000",
          });
        };
        bnt3Click() {
          this.setState({
            count: "9999",
          });
        }
​
        render() {
          const { count } = this.state;
          return (
            <div>
              {/* 1.this绑定方式一:bind绑定 */}
              <button onClick={this.bnt1Click.bind(this)}>按钮</button>
​
              {/* 2.this绑定方式二:ES6 class field */}
              <button onClick={this.bnt2Click}>按钮</button>
​
              {/* 3.this绑定方式三:直接传入一个箭头函数(zyao) */}
              <button onClick={() => console.log("bnt3Click")}>按钮</button>
              <button onClick={() => this.bnt3Click()}>按钮</button>
              <h2>{count}</h2>
            </div>
          );
        }
      }
      // 2.创建root并且渲染App组件
      const root = ReactDOM.createRoot(document.querySelector("#root"));
      root.render(<App />);
    </script>

事件参数传递

在执行事件函数时,有可能我们需要获取一些参数信息:比如event对象、其他参数

情况一:获取event对象

  • 很多时候我们需要拿到event对象来做一些事情(比如阻止默认行为)
  • 那么默认情况下,event对象有被直接传入,函数就可以获取到event对象;

情况二:获取更多参数

  • 有更多参数时,我们最好的方式就是传入一个箭头函数,主动执行的事件函数,并且传入相关的其他参数;
    <script type="text/babel">
      // 1.定义根组件
      class App extends React.Component {
        constructor() {
          super();
          this.state = {
            message: "Hello World",
          };
        }
​
        bntClick(event, name, age) {
          console.log(event, this);
          console.log(name, age);
        }
​
        render() {
          const { message } = this.state;
          return (
            <div>
              {/* 1.event参数的传递 */}
              <button onClick={this.bntClick.bind(this)}>按钮1</button>
              <button onClick={(event) => this.bntClick(event)}>按钮2</button>
​
              {/* 2.额外的参数传递 */}
              <button onClick={this.bntClick.bind(this, "kobe", 30)}>
                按钮3(不推荐)
              </button>
              <button onClick={(event) => this.bntClick(event, "kobe", 30)}>
                按钮4
              </button>
            </div>
          );
        }
      }
      // 2.创建root并且渲染App组件
      const root = ReactDOM.createRoot(document.querySelector("#root"));
      root.render(<App />);
    </script>

React条件渲染

某些情况下,界面的内容会根据不同的情况显示不同的内容,或者决定是否渲染某部分内容:

  • 在vue中,我们会通过指令来控制:比如v-if、v-show;
  • 在React中,所有的条件判断都和普通的JavaScript代码一致;

常见的条件渲染的方式有哪些呢?

方式一:条件判断语句

  • 适合逻辑较多的情况

方式二:三元运算符

  • 适合逻辑比较简单

方式三:与运算符&&

  • 适合如果条件成立,渲染某一个组件;如果条件不成立,什么内容也不渲染;
<script type="text/babel">
      // 1.定义根组件
      class App extends React.Component {
        constructor() {
          super();
          this.state = {
            message: "Hello World",
            isReady: true,
            friend: {
              name: "kobe",
              age: 30,
            },
          };
        }
​
        render() {
          const { message, isReady, friend } = this.state;
​
          // 1.条件判断一:使用if进行条件判断
          let showElement = null;
          if (isReady) {
            showElement = <h2>准备开始比赛吧</h2>;
          } else {
            showElement = <h1>请提前做好准备</h1>;
          }
​
          return (
            <div>
              {/* 1.根据条件判断 */}
              <div>{showElement}</div>
​
              {/* 2.三元运算符 */}
              <div>
                {isReady ? <button>开始战斗</button> : <h3>赶快准备</h3>}
              </div>
​
              {/* 3.&&逻辑与的运算符 */}
              {/* 场景:当取出来的friend可能为undefined时,使用&&进行条件判断 */}
              <div>{friend && <div>{friend.name + " " + friend.age}</div>}</div>
            </div>
          );
        }
      }
      // 2.创建root并且渲染App组件
      const root = ReactDOM.createRoot(document.querySelector("#root"));
      root.render(<App />);
    </script>

v-show的效果

  • 主要是控制display属性是否为none
<script type="text/babel">
      // 1.定义根组件
      class App extends React.Component {
        constructor() {
          super();
          this.state = {
            message: "Hello World",
            isShow: true,
          };
        }
        changeShow() {
          this.setState({
            isShow: !this.state.isShow,
          });
        }
​
        render() {
          const { message, isShow } = this.state;
​
          let showEl = null;
          if (isShow) {
            showEl = <h2>{message}</h2>;
          }
          return (
            <div>
              <button onClick={() => this.changeShow()}>切换</button>
              {isShow && <h2>{message}</h2>}
​
              {/* v-show的效果实现 */}
              <h2 style={{ display: isShow ? "block" : "none" }}>哈哈哈哈哈</h2>
            </div>
          );
        }
      }
      // 2.创建root并且渲染App组件
      const root = ReactDOM.createRoot(document.querySelector("#root"));
      root.render(<App />);
    </script>

React列表渲染

真实开发中我们会从服务器请求到大量的数据,数据会以列表的形式存储:

  • 比如歌曲、歌手、排行榜列表的数据;
  • 比如商品、购物车、评论列表的数据;
  • 比如好友消息、动态、联系人列表的数据;

在React中并没有像Vue模块语法中的v-for指令,而且需要我们通过JavaScript代码的方式组织数据,转成JSX:

  • 很多从Vue转型到React的同学非常不习惯,认为Vue的方式更加的简洁明了;
  • 但是React中的JSX正是因为和JavaScript无缝的衔接,让它可以更加的灵活;
  • 另外我经常会提到React是真正可以提高我们编写代码能力的一种方式;

如何展示列表呢?

  • 在React中,展示列表最多的方式就是使用数组的map高阶函数;

很多时候我们在展示一个数组中的数据之前,需要先对它进行一些处理:

  • 比如过滤掉一些内容:filter函数
  • 比如截取数组中的一部分内容:slice函数
 <script type="text/babel">
      // 1.定义根组件
      class App extends React.Component {
        constructor() {
          super();
          this.state = {
            students: [
              { id: 123, name: "why12", score: 199 },
              { id: 154, name: "why23", score: 91 },
              { id: 143, name: "why45", score: 195 },
              { id: 146, name: "why42", score: 198 },
            ],
          };
        }
​
        render() {
          const { message, students } = this.state;
​
          // 分数大于100的学生显示
          const filterStudents = students.filter((item) => {
            return item.score > 100;
          });
​
          // 分数大于100,而且只显示2个人
          const sliceStudents = filterStudents.slice(0, 2);
​
          return (
            <div>
              <h2>学生列表数据</h2>
              <div className="list">
                {students
                  .filter((item) => {
                    return item.score > 100;
                  })
                  .slice(0, 2)
                  .map((item) => {
                    return (
                      <div className="item" key={item.id}>
                        <h2>学号:{item.id}</h2>
                        <h3>姓名:{item.name}</h3>
                        <h1>分数:{item.score}</h1>
                      </div>
                    );
                  })}
              </div>
            </div>
          );
        }
      }
      // 2.创建root并且渲染App组件
      const root = ReactDOM.createRoot(document.querySelector("#root"));
      root.render(<App />);
    </script>

列表中的key

我们会发现在前面的代码中只要展示列表都会报一个警告:

image-20221017145731930

这个警告是告诉我们需要在列表展示的jsx中添加一个key。

  • key主要的作用是为了提高diff算法时的效率;
<div className="item" key={item.id}>
  <h2>学号:{item.id}</h2>
</div>

JSX的本质

实际上,jsx 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖。

  • 所有的jsx最终都会被转换成React.createElement的函数调用。

createElement需要传递三个参数:

◼ 参数一:type

  • 当前ReactElement的类型;
  • 如果是标签元素,那么就使用字符串表示 “div”;
  • 如果是组件元素,那么就直接使用组件的名称;

◼ 参数二:config

  • 所有jsx中的属性都在config中以对象的属性和值的形式存储;
  • 比如传入className作为元素的class;

◼ 参数三:children

  • 存放在标签中的内容,以children数组的方式进行存储;
  • 当然,如果是多个元素呢?React内部有对它们进行处理,处理的源码在下方

image-20221017145937414

直接编写jsx代码

我们自己来编写React.createElement代码:

  • 我们就没有通过jsx来书写了,界面依然是可以正常的渲染。

  • 另外,在这样的情况下,你还需要babel相关的内容吗?不需要了

    • 所以,type="text/babel"可以被我们删除掉了;
    • 所以,< script src="../react/babel.min.js">< /script>可以被我们删除掉了;
<script>
      // 1.定义根组件
      class App extends React.Component {
        constructor() {
          super();
          this.state = {
            message: "Hello World",
          };
        }
​
        render() {
          const { message } = this.state;
​
          const element = /*#__PURE__*/ React.createElement(
            "div",
            null,
            /*#__PURE__*/ React.createElement(
              "div",
              {
                className: "header",
              },
              "Header"
            ),
            /*#__PURE__*/ React.createElement(
              "div",
              {
                className: "content",
              },
              /*#__PURE__*/ React.createElement("div", null, "Banner"),
              /*#__PURE__*/ React.createElement(
                "ul",
                null,
                /*#__PURE__*/ React.createElement(
                  "li",
                  null,
                  "\u5217\u8868\u6570\u636E1"
                ),
                /*#__PURE__*/ React.createElement(
                  "li",
                  null,
                  "\u5217\u8868\u6570\u636E2"
                ),
                /*#__PURE__*/ React.createElement(
                  "li",
                  null,
                  "\u5217\u8868\u6570\u636E3"
                ),
                /*#__PURE__*/ React.createElement(
                  "li",
                  null,
                  "\u5217\u8868\u6570\u636E4"
                ),
                /*#__PURE__*/ React.createElement(
                  "li",
                  null,
                  "\u5217\u8868\u6570\u636E5"
                ),
                /*#__PURE__*/ React.createElement(
                  "li",
                  null,
                  "\u5217\u8868\u6570\u636E6"
                )
              )
            ),
            /*#__PURE__*/ React.createElement(
              "div",
              {
                className: "footer",
              },
              "Footer"
            )
          );
          return element;
        }
      }
      // 2.创建root并且渲染App组件
      const root = ReactDOM.createRoot(document.querySelector("#root"));
      root.render(React.createElement(App, null));
    </script>

什么是虚拟DOM?虚拟DOM在React中起到什么作用?

  • 虚拟DOM diff
  • 跨平台渲染
  • 声明式编程

虚拟DOM的创建过程

我们通过 React.createElement 最终创建出来一个 ReactElement对象:

这个ReactElement对象是什么作用呢?React为什么要创建它呢?

  • 原因是React利用ReactElement对象组成了一个JavaScript的对象树;
  • JavaScript的对象树就是虚拟DOM(Virtual DOM);

如何查看ReactElement的树结构呢?

  • 我们可以将之前的jsx返回结果进行打印;
  • 注意下面代码中我打jsx的打印;

而ReactElement最终形成的树结构就是Virtual DOM;

image-20221017150311251

声明式编程

虚拟DOM帮助我们从命令式编程转到了声明式编程的模式

React官方的说法: Virtual DOM 是一种编程理念。

  • 在这个理念中,UI以一种理想化或者说虚拟化的方式保存在内存中,并且它是一个相对简单的JavaScript对象
  • 我们可以通过ReactDOM.render让 虚拟DOM 和 真实DOM同步起来,这个过程中叫做协调(Reconciliation);

◼ 这种编程的方式赋予了React声明式的API:

  • 你只需要告诉React希望让UI是什么状态;
  • React来确保DOM和这些状态是匹配的;
  • 你不需要直接进行DOM操作,就可以从手动更改DOM、属性操作、事件处理中解放出来;

阶段案例练习

◼ 1.在界面上以表格的形式,显示一些书籍的数据;

◼ 2.在底部显示书籍的总价格;

◼ 3.点击+或者-可以增加或减少书籍数量(如果为1,那么不能继续-);

◼ 4.点击移除按钮,可以将书籍移除(当所有的书籍移除完毕时,显示:购物车为空~);

<!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>购物车案例</title>
    <style>
      table {
        border-collapse: collapse;
        text-align: center;
      }
      thead {
        background-color: #f2f2f2;
      }
      th,
      td {
        padding: 10px 16px;
        border: 1px solid #aaa;
      }
    </style>
  </head>
  <body>
    <div id="root"></div>
​
    <script src="../lib/react.js"></script>
    <script src="../lib/react-dom.js"></script>
    <script src="../lib/bable.js"></script>
​
    <script src="./data.js"></script>
    <script src="./format.js"></script>
​
    <script type="text/babel">
      // 1.定义根组件
      class App extends React.Component {
        constructor() {
          super();
          this.state = {
            books: books,
          };
        }
​
        getTotalPrice() {
          const totalPrice = this.state.books.reduce((preValue, item) => {
            return preValue + item.count * item.price;
          }, 0);
          return totalPrice;
        }
​
        increment(index) {
          const newBooks = [...this.state.books];
          newBooks[index].count += 1;
          this.setState({
            books: newBooks,
          });
        }
​
        decrement(index) {
          const newBooks = [...this.state.books];
          newBooks[index].count -= 1;
          this.setState({
            books: newBooks,
          });
        }
​
        changeCount(index, count) {
          const newBooks = [...this.state.books];
          newBooks[index].count += count;
          this.setState({
            books: newBooks,
          });
        }
​
        removeItem(index) {
          const newBooks = [...this.state.books];
          newBooks.splice(index, 1);
          this.setState({
            books: newBooks,
          });
        }
​
        renderBookList() {
          const { books } = this.state;
          return (
            <div>
              <table>
                <thead>
                  <tr>
                    <th>序号</th>
                    <th>书籍名称</th>
                    <th>出版日期</th>
                    <th>价格</th>
                    <th>购买数量</th>
                    <th>操作</th>
                  </tr>
                </thead>
                <tbody>
                  {books.map((item, index) => {
                    return (
                      <tr key={item.id}>
                        <td>{index + 1}</td>
                        <td>{item.name}</td>
                        <td>{item.date}</td>
                        <td>{formatPrice(item.price)}</td>
                        <td>
                          <button
                            disabled={item.count <= 1}
                            onClick={() => this.changeCount(index, -1)}
                          >
                            -
                          </button>
                          {item.count}
                          <button onClick={() => this.changeCount(index, 1)}>
                            +
                          </button>
                        </td>
                        <td>
                          <button onClick={() => this.removeItem(index)}>
                            删除
                          </button>
                        </td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
              <h2>总价格:{formatPrice(this.getTotalPrice())}</h2>
            </div>
          );
        }
​
        renderBookEmpty() {
          return (
            <div>
              <h2>购物车为空,请添加书籍</h2>
            </div>
          );
        }
​
        render() {
          const { books } = this.state;
​
          // 1.计算总价格方式一:
          // let totalPrice = 0;
          // for (let i = 0; i < books.length; i++) {
          //   const book = books[i];
          //   totalPrice += book.price * book.count;
          // }
​
          // 2.计算总价格的方式二:
          // const totalPrice = books.reduce((preValue, item) => {
          //   return preValue + item.count * item.price;
          // }, 0);
​
          return books.length ? this.renderBookList() : this.renderBookEmpty();
        }
      }
      // 2.创建root并且渲染App组件
      const root = ReactDOM.createRoot(document.querySelector("#root"));
      root.render(<App />);
    </script>
  </body>
</html>