Skip to content

🔥更新:2024-12-09📝字数: 0 字⏱时长: 0 分钟

第一章:React 是什么?

1.1 概述

  • React 是用于构建 Web原生交互界面

image-20231213090611708

1.2 目前 React 在前端处于什么地位?

  • 目前,前端最流行的三大框架是:Vue、React 和 Angular。

image-20231213092449367

  • Google 对于前端三大框架的搜索指数:

image-20231213092750683

1.3 React 的技术特点

  • ① React 是由前 Facebook(现 Meta)公司来更新和维护的,它是大量优秀程序员思想结晶
  • ② React 的流行不仅仅局限于普通开发工程师对它的认可,大量的其它框架借鉴了 React 的思想。
    • Vue.js 框架在设计之初,也借鉴和学习了 React,如:React 的 Hooks (Vue 3.x 的 Composition API)。
  • ③ React 是前端的先驱者,它总是会引领整个前端的潮流

第二章:React 入门

2.1 React 的介绍

2.1.1 概述

  • React 是用于构建 Web原生交互界面

image-20231213095930226

2.1.2 React 的特点

  • ① 声明式编程:
    • 目前,整个大前端的最流行开发模式声明式编程,如:Vue、React、Flutter 等。
    • 声明式编程允许我们只维护自己的状态当状态改变的时候React 会根据最新的状态去重新渲染我们的 UI 界面

image-20231213100244515

  • ② 组件化开发:
    • 组件化开发是目前前端的流行趋势,我们可以将复杂的界面拆分为一个个小的组件。

image-20231213100607613

  • ③ 多平台适配:
    • 2013 年,React 发布之初主要是开发 WEB 页面
    • 2015 年,Facebook 推出了 ReactNative,用于开发移动端跨平台
    • 2017 年,Facebook 推出了 ReactVR,用于开发虚拟现实 WEB 应用程序
    • ……

image-20231213100850354

2.2 React 入门(⭐)

2.2.1 React 的开发依赖

  • React 开发需要依赖三个库:

    • react:包含 React 所必须得核心代码。
    • react-dom:React 渲染到不同平台所需要的核心代码。
    • babel:将 jsx 转换为 React 代码的工具。
  • 我们可以在 bootcdn 中搜索:

image-20231213105501661

注意⚠️:

  • 我们会发现有 umd 和 cjs 的版本。
  • 其中,umd 全称是 Universal Module Definition,可以在前端和后端通用。
  • 其中,cjs 全称 CommonJS ,不能在浏览器中运行,通常会在 Node.js 中运行。
  • 其中,amd 的全称是 Asynchronous Module Definition,已经被淘汰
  • 其中,esm 的全称是 ES Modules,可以在很多现代浏览器中使用;但是,一般配合打包工具,如:webpack、vite 等使用,以便在所有浏览器都能使用。
  • 示例:
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
</head>
<body>

  <!-- react:React 的核心 -->
  <script crossorigin src="https://cdn.bootcdn.net/ajax/libs/react/18.2.0/umd/react.development.js"></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script crossorigin src="https://cdn.bootcdn.net/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script crossorigin src="https://cdn.bootcdn.net/ajax/libs/babel-standalone/7.22.17/babel.js"></script>

</body>
</html>

2.2.2 React 入门

  • 需求:在页面中展示 "Hello React"

image-20231213154348468

  • 示例:
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script crossorigin src="https://cdn.bootcdn.net/ajax/libs/react/18.2.0/umd/react.development.js"></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script crossorigin src="https://cdn.bootcdn.net/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script crossorigin src="https://cdn.bootcdn.net/ajax/libs/babel-standalone/7.22.17/babel.js"></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class="root"></div>
 
  <script type="text/babel">
    // 创建一个虚拟 DOM
    const vDOM = (
        <h1>Hello React</h1>
    )
    // React 18之后,创建 React 根元素
    const root = ReactDOM.createRoot(document.querySelector('.root'))
    root.render(vDOM)
  </script>

</body>
</html>

2.2.3 注意点

  • script 元素的 type 属性必须是 text/babel,即告诉浏览器此处让 babel 解析 jsx 的语法。
  • ReactDOM.createRoot(xxx)的作用是创建一个 React 根元素,然后将虚拟 DOM 渲染到这个根元素之中。其中,参数 xxx 就是要挂在到哪一个 HTML 元素上。
  • root.render(xxx) 是用来渲染组件(虚拟 DOM )的。
  • ④ 在 React 18 之前的语法是:ReactDOM.render(vDOM, document.querySelector('.root'));不过,现在该 API 已经过时。
  • ⑤ 我们可以使用 {} 在 jsx 中引入外部的变量或表达式。
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script crossorigin src="https://cdn.bootcdn.net/ajax/libs/react/18.2.0/umd/react.development.js"></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script crossorigin src="https://cdn.bootcdn.net/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script crossorigin src="https://cdn.bootcdn.net/ajax/libs/babel-standalone/7.22.17/babel.js"></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class="root"></div>

  <script type="text/babel">
    // 创建一个虚拟 DOM
    let message = "你好,React"
    const vDOM = (
        <h1>
          {/* 可以使用 {} 来外部的变量或表达式 */ }
          {message}
        </h1>
    )
    // React 18之后,创建 React 根元素 
    const root = ReactDOM.createRoot(document.querySelector('.root'))
    root.render(vDOM)
  </script>

</body>
</html>
  • ⑥ React 18 之后,为什么要 ReactDOM.createRoot(document.querySelector('.root')) 这样创建根元素?是因为,React 18 之后可以创建多个根元素:
jsx
const root = ReactDOM.createRoot(document.querySelector('.root'))
root.render(vDOM)
const app = ReactDOM.createRoot(document.querySelector('.app'))
app.render(vDOM)

2.3 组件化开发(⭐)

2.3.1 引入

  • 需求:在页面中,通过点击按钮切换展示的文本。

  • 示例:
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))
    // 定义变量
    let message = '你好,React'
    // 定义方法
    const change = () => {
      // 修改数据
      message = '你好,React18'
      // 重新渲染页面
      appRender()
    }

    // 渲染页面
    appRender()

    function appRender() {
      // 创建一个虚拟 DOM
      const vDOM = (
          <div>
            <h1>{message}</h1>
            { /* 注意:
              ① 在 React 中,事件的名称是以 onXxx() 的形式,小驼峰的命名规则
              ② onClick 中传入的是函数的名称,如:change,而不是 change() ;否则,当点击的时候,就会没有效果,因为 change() 返回的是 undefined
            */ }
            <button onClick={change}>改变文本</button>
          </div>
      )
      // 将虚拟 DOM 渲染到页面上
      app.render(vDOM)
    }

  </script>

</body>
</html>

2.3.2 问题分析

  • 上述的代码虽然能解决问题,但是却产生了如下的问题:

    • ① 我们需要手动封装渲染页面的函数,在初次渲染的时候就调用该函数;当我们更改完数据之后,还需要手动调用该函数;难道每次修改完数据,都需要手动调用该函数,React 也太不智能了吧(这是所谓的声明式编程?)。
    • ② 代码过于分散,如果开发过 Vue3.x 的都知道,Vue 会将代码封装到 .vue 文件中;那么,React 应该也提供类似的功能,而上述代码的实现却没能很好的体现程序设计的内聚性。
    • ③ 我们也知道root.render(xxx) 不仅可以用来渲染 DOM ,也可以用来渲染组件的;其中,xxx 既可以是虚拟 DOM ,也可以是组件
    • ④ ……
  • 那么,怎么解决?

image-20231213161554404

注意⚠️:React 中支持类组件函数式组件,我们以类组件切入点讲解,函数式组件后面再讲解。。

2.3.3 React 中的类组件

  • 定义:

    • ① 定义一个类,继承自 React.Component 类,类名必须采用大驼峰的原则,
    • ② 自定义的类需要重写 render() 方法,该方法中返回虚拟 DOM(其实,就是 jsx)。
    • ③ 在 root.render(xxx) 方法中,传入 <类/>
  • 示例:

html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建一个组件
    class App extends React.Component {

      // 渲染内容 render 方法
      render() {
        return (
            <h2>Hello React</h2>
        )
      }
    }

    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))
    // 渲染组件
    app.render(<App/>)
  </script>

</body>
</html>

2.3.4 React 中的数据依赖

  • 我们知道,在 Vue 3.x 中的 Options API 中,我们是将数据封装到 data(){} 配置中的;那么,在 React 中写到哪里?

  • 如何让一个类组件的实例获取到实例的数据,有很多实现方式;其中,最简单的就是在构造器中定义了。

  • 同时,定义在组件中的数据,我们会分为两类:

    • 参与界面更新的数据:当数据发生变化的时候,需要更新组件渲染的内容。
    • 不参与界面更新的数据:当数据发生变化的时候,不需要更新组件渲染的内容。
  • 所以,参与界面更新的数据,就需要定义在构造器中,并且定义在当前对象的 state 属性中;而且,当数据发生变化的时候,我们不是通过修改 this.state 属性的值,而是通过 this.setState() 方法来更新数据,以便通知 React 来重新渲染页面。

  • 示例:

html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建一个组件
    class App extends React.Component {
      // 组件数据
      constructor() {
        super()
        // 参与界面更新的数据,需要写在 this.state 中
        this.state = {
          isHot: false
        }
      }

      // 组件方法
      change() {
        // 内部完成了两件事情
        // ① 将 state 中的 isHot 修改了
        // ② 自动重新执行 render 函数
        this.setState({
          isHot: !this.state.isHot
        })
      }

      // 渲染内容 render 方法
      render() {
        const {isHot} = this.state
        return (
            <div>
              <h2>{'今天天气很' + (isHot ? '炎热' : '凉爽')}</h2>
              <button onClick={this.change}>改变内容</button>
            </div>
        )
      }
    }

    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))
    // 渲染组件
    app.render(<App/>)
  </script>

</body>
</html>

2.3.5 this 绑定问题的解决

  • 当我们运行上述的程序,会发生 thisundefined

image-20231213163531249

  • 官方是如何解决的?

image-20231214101552644

  • 那么,我们可以修改下程序:
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建一个组件
    class App extends React.Component {
      // 组件数据
      constructor() {
        super()
        this.state = {
          isHot: false
        }
        // 绑定 this
        this.change = this.change.bind(this)
      }

      change() {
        this.setState({
          isHot: !this.state.isHot
        })
      }

      render() {
        const {isHot} = this.state
        return (
            <div>
              <h2>{'今天天气很' + (isHot ? '炎热' : '凉爽')}</h2>
              { /* 注意:此处不能是 this.change() ,因为我们需要传递函数地址给 React,让其帮我们调用 */ }
              { /* 注意:如果写成 this.change() ,就是手动调用方法,然后方法返回 undefined,是错误写法! */ }
              <button onClick={this.change}>改变内容</button>
            </div>
        )
      }
    }

    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))
    // 渲染组件
    app.render(<App/>)
  </script>

</body>
</html>
  • 再次查看浏览器:

  • 为什么要这么写?我们可以通过如下的代码,来模拟一下:
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script>
    // 创建一个组件
    class App  {
      constructor() {
        this.message = "Hello World"
      }

      change(){
        this.message = "Hello React"
        console.log(this,this.message)
      }
    }

    const app = new App()
    app.change()

  </script>

</body>
</html>
  • 运行如下:

image-20231213170029391

  • 如果,我们将代码修改一下:
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script>
    // 创建一个组件
    class App  {
      constructor() {
        this.message = "Hello World"
      }

      change(){
        this.message = "Hello React"
        console.log(this,this.message)
      }
    }

    const app = new App()

    const abc = app.change

    abc()

  </script>

</body>
</html>
  • 此时,结果如下所示:

image-20231213170219827

  • 分析:代码之所以报错,是因为 this 的隐式绑定。当运行 const app = new App()的时候,change() 方法的 this 确实是 app 对象;但是,当运行const abc = app.change; abc() 这段代码的时候,change() 方法的 this 已经是 window 对象。又因为我们使用的是类,浏览器会开启严格模式,所以 change() 方法的 this 已经是 undefined 对象,此时 undefined.message 当然会报错了。

  • 我们可以通过 bind 来显示改变 this 的指向,并且 bind 的定义是这样的:

image-20231213170908342

  • 修改下代码:
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script>
    // 创建一个组件
    class App  {
      constructor() {
        this.message = "Hello World"
      }

      change(){
        this.message = "Hello React"
        console.log(this,this.message)
      }
    }

    const app = new App()

    let abc = app.change
    
    // 通过 bind 来显示改变 this 的指向
    abc = abc.bind(app)

    abc()

  </script>

</body>
</html>
  • 运行一下,查看结果:

image-20231213171024665

  • 此时,我们再来分析一下 React 中的代码:

image-20231213171348143

  • 当然,你也可以这么写,原理是一样的:
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建一个组件
    class App extends React.Component {
      // 组件数据
      constructor() {
        super()
        // 参与界面更新的数据,需要写在 this.state 中
        this.state = {
          isHot: false
        }
      }

      // 组件方法
      change() {
        // 内部完成了两件事情
        // ① 将 state 中的 isHot 修改了
        // ② 自动重新执行 render 函数
        this.setState({
          isHot: !this.state.isHot
        })
      }

      // 渲染内容 render 方法
      render() {
        const {isHot} = this.state
        return (
            <div>
              <h2>{'今天天气很' + (isHot ? '炎热' : '凉爽')}</h2>
              { /* 注意:此处需要手动绑定 this  */ }
              <button onClick={this.change.bind(this)}>改变内容</button>
            </div>
        )
      }
    }

    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))
    // 渲染组件
    app.render(<App/>)
  </script>

</body>
</html>

2.4 React 案例(⭐)

2.4.1 数组列表案例

  • 需求:需要将数组中的元素渲染到页面上。

image-20231214102502634

注意⚠️:

  • 在 React 中,没有 Vue 的差值语法;React 中的 jsx 语法很简单,都是 {}
  • React 中 {} 可以插入数组,会自动遍历数组中的元素并渲染到页面上;但是,不支持对象。
  • 示例:实现方式一(通过数组的索引,不推荐)
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建一个组件
    class App extends React.Component {

      constructor() {
        super();
        // 组件数据
        this.state = {
          message: '你好,React',
          movies: ['星际穿越', '大话西游', '梦幻西游', '诛仙']
        }
      }

      // 渲染内容 render 方法
      render() {
        const {message, movies} = this.state       

        return (
            <div>
              <h2>{message}</h2>
              <ul>
                {/* 通过数组的索引来实现,不推荐 */}
                <li>{this.state.movies[0]}</li>
                <li>{this.state.movies[1]}</li>
                <li>{this.state.movies[2]}</li>
                <li>{this.state.movies[3]}</li>
              </ul>
            </div>
        )
      }
    }

    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))
    // 渲染组件
    app.render(<App/>)
  </script>

</body>
</html>
  • 示例:实现方式二(通过手动拼接数组)
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建一个组件
    class App extends React.Component {

      constructor() {
        super();
        // 组件数据
        this.state = {
          message: '你好,React',
          movies: ['星际穿越', '大话西游', '梦幻西游', '诛仙']
        }
      }

      // 渲染内容 render 方法
      render() {
        const {message, movies} = this.state

        // 通过手动拼接数组
        let liEls = [];
        movies.forEach((item, index) => {
          liEls.push(<li key={index}>{item}</li>)
        })

        return (
            <div>
              <h2>{message}</h2>
              <ul>
                {/* jsx 中的 {} 支持放入数组,jsx 会自动遍历数组,并将数组中的元素渲染到页面上;但是,不支持对象。 */}
                {liEls}
              </ul>
            </div>
        )
      }
    }

    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))
    // 渲染组件
    app.render(<App/>)
  </script>

</body>
</html>
  • 示例:实现方式三(通过数组的 map 方法,用于对数组中的每个元素进行操作,并返回一个新的数组)
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建一个组件
    class App extends React.Component {

      constructor() {
        super();
        // 组件数据
        this.state = {
          message: '你好,React',
          movies: ['星际穿越', '大话西游', '梦幻西游', '诛仙']
        }
      }

      // 渲染内容 render 方法
      render() {
        const {message, movies} = this.state

        return (
            <div>
              <h2>{message}</h2>
              <ul>
                {/* jsx 中的 {} 支持放入数组,jsx 会自动遍历数组,并将数组中的元素渲染到页面上;但是,不支持对象。*/}
                {/* 可以通过数组的 map 方法实现。*/}
                {
                  movies.map((item,index) => <li key={index}>{item}</li>)
                }
              </ul>
            </div>
        )
      }
    }

    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))
    // 渲染组件
    app.render(<App/>)
  </script>

</body>
</html>

2.4.2 计数器案例

  • 需求:对计数进行 +1-1 操作。

  • 示例:
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建一个组件
    class App extends React.Component {

      constructor() {
        super();
        // 组件数据
        this.state = {
          count: 0
        }
      }

      increment() {
        const {count} = this.state
        this.setState({
          count: count + 1
        })
      }

      decrement() {
        const {count} = this.state
        this.setState({
          count: count - 1
        })
      }

      // 渲染内容 render 方法
      render() {
        const {count} = this.state

        return (
            <div>
              <h2>当前计数为:{count}</h2>
              <button onClick={this.increment.bind(this)}>点我+1</button>
              <button onClick={this.decrement.bind(this)}>点我-1</button>
            </div>
        )
      }
    }

    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))
    // 渲染组件
    app.render(<App/>)
  </script>

</body>
</html>

第三章:JSX 语法

3.1 初识 JSX 语法(⭐)

  • 像下面的一段代码:
jsx
const element = <h1>Hello, world!</h1>
  • 这个有趣的标签语法既不是字符串不是 HTML ;如果,我们将 script 元素的 type='text/babel' 属性去掉,就会报错,因为浏览器压根不认识。
  • 其实,这就是 JSXJavaScript eXtension),是一种 JavaScript 的语法扩展;也有人称其是 JavaScript XML,因为看起来就像 XML 语法。
  • 在 React 开发中,建议配合使用 JSX ,因为其可以很好地描述 UI 应该呈现出它应有交互的本质形式。
  • JSX 不同于 Vue 中的模板语法;因此,我们不再需要专门学习模板语法中的各种指令,如:v-ifv-elsev-for 等。
  • JSX 拥有 JavaScript 的一切功能!!!

3.2 为什么 React 选择了 JSX ?(⭐)

  • React 认为渲染逻辑本质上与其他 UI 逻辑存在内在耦合,如:
    • 在 UI 中需要绑定处理事件。
    • 在某些时刻状态发生变化时需要通知到 UI。
    • 需要在 UI 中展示准备好的数据。
  • React 并没有采用将标记与逻辑分离到不同文件这种人为的分离方式,而是通过将二者共同存放在称之为组件的松散耦合单元之中,来实现关注点分离

维基百科的定义:

  • 计算机科学中,关注点分离(Separation of concerns,SoC),是将计算机程序分隔为不同部分的设计原则。每一部分会有各自的关注焦点。关注焦点是影响计算机程式程式码的一组资讯。关注焦点可以像是将程式码优化过的硬件细节一般,或者像实例化类别的名称一样具体。展现关注点分离设计的程序被称为模组化程序[1]。模组化程度,也就是区分关注焦点,通过将资讯封装在具有明确界面的程序代码段落中。封装是一种资讯隐藏手段[2]。资讯系统中的分层设计是关注点分离的另一个实施例(例如,表示层,业务逻辑层,数据访问层,持久数据层)[3]
  • 关注点分离,是对只与“特定概念、目标”(关注点)相关联的软件组成部分进行“标识、封装和操纵”的能力,即标识、封装和操纵关注点的能力。是处理复杂性的一个原则。由于关注点混杂在一起会导致复杂性大大增加,所以能够把不同的关注点分离开来,分别处理就是处理复杂性的一个原则,一种方法。分离关注点使得解决特定领域问题的程式码从业务逻辑中独立出来,业务逻辑的程式码中不再含有针对特定领域问题程式码的调用(将针对特定领域问题程式码抽象化成较少的程式码,例如将程式码封装成function或是class),业务逻辑同特定领域问题的关系通过侧面来封装、维护,这样原本分散在整个应用程序中的变动就可以很好的管理起来。
  • 关注点分离的价值在于简化计算机程序的开发和维护。当关注点分开时,各部分可以重复使用,以及独立开发和更新。具有特殊价值的是能够稍后改进或修改一段代码,而无需知道其他部分的细节必须对这些部分进行相应的更改。
  • React不强制要求使用 JSX,但是大多数人发现,在 JavaScript 代码中将 JSX 和 UI 放在一起时,会在视觉上有辅助作用。
  • JSX 还可以使 React 显示更多有用的错误和警告消息。

3.3 JSX 的基本使用(⭐)

3.3.1 JSX 的基本语法

  • JSX 的基本语法:
    • ① JSX 中只能一个根元素,除非使用 Fragment 标签包裹。
    • ② 为了方便阅读,我们通常会在 JSX 的外层包裹一个 ()
    • ③ 在 JSX 中,标签可以是单标签,也可以是双标签;但是,如果是单标签,必须以 <标签 /> 结尾(这点,很像 XML 语法)。

注意⚠️:JSX 是在类式组件render() 方法的 return 后面书写,函数式组件且听后文讲解。

  • 示例:
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {
      // 组件数据
      state = {
        message : '你好,React'
      }

      // 渲染内容 render 方法
      /* ① jsx 中顶层只能有一个根元素,除非使用 Fragment 标签包裹 */
      /* ② 为了方便阅读,我们通常会在 jsx 的外层包裹一个 () */
      /* ③ jsx 中的标签可以单标签,也可以是双标签;但是,如果是单标签,必须以 <标签 /> 结尾 */
      render() {
        return (
          <div>
            {this.state.message}
          </div>
        )
      }
    }

    // 渲染组件
    app.render(<App />)
  </script>

</body>
</html>

3.3.2 JSX 语法中的注释

  • JSX 语法中的注释只有一种,就是 { /* 我是注释内容 */ }

  • 示例:

html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {
      // 组件数据
      state = {
        message : '你好,React'
      }

      // 渲染内容 render 方法
      render() {
        return (
          <div>
            { /* 我是注释内容 */ }
            {this.state.message}
          </div>
        )
      }
    }

    // 渲染组件
    app.render(<App />)
  </script>

</body>
</html>

3.3.3 JSX 中嵌入变量作为子元素

3.3.3.1 概述

  • 在 Vue 中,如果是插入文本,需要使用插值语法 ;如果需要插入大段子元素(如:ul 中插入大量的 li),需要使用 v-for 等指令。

  • 在 React 中,可以使用 {} 中嵌入变量的方式来作为子元素;换言之,结合了 Vue 中的上述两种写法。

  • 示例:插入变量作为文本

html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {
      // 组件数据
      state = {
        message: '你好,React',
      }

      // 渲染内容 render 方法
      render() {
        const { message } = this.state
        return (
          <div>
            {/* jsx 中嵌入变量作为子元素 */}
            {/* 插入变量作为文本 */}
            <h2>{message}</h2>
          </div>
        )
      }
    }

    // 渲染组件
    app.render(<App />)
  </script>

</body>
</html>
  • 示例:插入变量作为子元素
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {
      // 组件数据
      state = {
        movies: ['星际穿越', '大话西游', '梦幻西游', '诛仙'],
      }

      // 渲染内容 render 方法
      render() {
        const { movies } = this.state
        return (
          <div>
            {/* jsx 中嵌入变量作为子元素 */}
            <ul>
              {
                movies.map((item,index) => <li key={index}>{item}</li>)
              }
            </ul>
          </div>
        )
      }
    }

    // 渲染组件
    app.render(<App />)
  </script>

</body>
</html>

3.3.3.2 JSX 中嵌入变量的各种情况

  • 情况①:当变量NumberStringArray 类型的时候,可以直接显示;当然,Array 类型会帮助我们遍历数组中的元素并显示到界面上。
  • 情况②:当变量nullundefinedBoolean 的时候,显示的内容为空

注意⚠️:如果依然希望可以显示 nullundefinedBoolean ,需要转换为 String(实际开发中,一般不需要)。

  • 情况③:当变量Object 对象类型的时候,会直接报错(not valid as a React child)。

  • 示例:情况①

html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {
      // 组件数据
      state = {
        message: '你好,React', // 字符串
        movies: ['星际穿越', '大话西游', '梦幻西游', '诛仙'], // 数组
        name: '张三', // 字符串
        age: 18 // 数值
      }

      // 渲染内容 render 方法
      render() {
        const { name,age, message,movies } = this.state
        return (
          <div>
            {/* jsx 中嵌入变量作为子元素 */}
            <h2>{message}</h2>
            <ul>
              {
                movies.map((item,index) => <li key={index}>{item}</li>)
              }
            </ul>
            <h2>我的名字是:{name},我的年龄是:{age}</h2>
          </div>
        )
      }
    }

    // 渲染组件
    app.render(<App />)
  </script>

</body>
</html>
  • 示例:情况②
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {
      // 组件数据
      state = {
        aaa: undefined,
        bbb: null,
        ccc: true,
      }

      // 渲染内容 render 方法
      render() {
        const { aaa,bbb, ccc } = this.state
        return (
            <div>
              {/* jsx 中嵌入变量作为子元素 */}
              <div>{aaa}</div>
              <div>{bbb}</div>
              <div>{ccc}</div>
              {/*之所以会显示,是因为转换为 String 类型;实际开发中,没有必要*/}
              <div>{ccc.toString()}</div>
            </div>
        )
      }
    }

    // 渲染组件
    app.render(<App/>)
  </script>

</body>
</html>
  • 示例:情况③
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {
      // 组件数据
      state = {
        obj: { name: '张三', age: 18 }, // 对象类型
      }

      // 渲染内容 render 方法
      render() {
        const { obj } = this.state
        return (
            <div>
              {/* jsx 中嵌入变量作为子元素 */}
              {/* 如果是对象类型,会直接报错;那么,将对象中的每个属性获取就可以了!!! */}
              <div>{obj.name} - {obj.age}</div>
            </div>
        )
      }
    }

    // 渲染组件
    app.render(<App/>)
  </script>

</body>
</html>

3.3.3.3 JSX 中嵌入表达式

  • Vue 中的插值语法中可以嵌入表达式;React 中的 JSX 中也可以。

  • React 中的 JSX 中嵌入的表达式常见的有:运算表达式三元表达式执行一个函数

  • 示例:

html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {

      state = {
        isHot: false
      }

      show(){
        return this.state.isHot
      }

      // 渲染内容 render 方法
      render() {
        const {isHot} = this.state
        return (
            <div>
              {/* jsx 中嵌入变量作为子元素 */}
              {/* 插入表达式: 运算表达式 */}
              <div>{10 + 20}</div>
              <div>{"张" + "三"}</div>
              {/* 插入表达式: 三元运算符 */}
              <div>今天天气很{isHot ? '炎热' : '凉爽'}</div>
              {/* 插入表达式: 执行一个函数 */}
              <div>{this.show().toString()}</div>
            </div>
        )
      }
    }

    // 渲染组件
    app.render(<App/>)
  </script>

</body>
</html>

3.4 JSX 绑定属性(⭐)

3.4.1 JSX 绑定基本属性

  • 在 Vue 中,对于元素身上的基本属性是通过 v-bind 指令或 : 语法糖来实现的:
vue
<template>
	<h2 :title="title">我是h2元素</h2>
</template>

<script setup>
    import {ref} from 'vue'
	const title = ref('哈哈')
</script>
vue
<template>
	<img :src="imgUrl" alt='' width='200px' />
</template>

<script setup>
    import {ref} from 'vue'
	const imgUrl = ref('https://mei.png')
</script>
vue
<template>
	<a :href="href" target='_blank'>百度一下,你就知道</a>
</template>

<script setup>
    import {ref} from 'vue'
	const href = ref('https://www.baidu.com')
</script>
  • 在 React 中,依然是使用 {} 来绑定基本属性。

  • 示例:

html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {
      // 组件数据
      state = {
        title: '哈哈哈',
        imgUrl: 'https://t7.baidu.com/it/u=4071610061,2917123939&fm=193&f=GIF',
        href: 'https://www.baidu.com/',
      }

      // 渲染内容 render 方法
      render() {
      const { title, imgUrl, href } = this.state
        return (
          <div>
            { /* 基本属性绑定 */}
            <h2 title={title}>我是h2元素</h2>
            <img src={imgUrl} alt='' width='200px' /> <br />
            <a href={href} target='_blank'>百度一下,你就知道</a>
          </div>
        )
      }
    }

    // 渲染组件
    app.render(<App />)
  </script>

</body>
</html>

3.4.2 JSX 绑定 style 属性

  • 在 Vue 中,对于 style 属性,是通过绑定 JavaScript 的对象值来实现的:
vue
<template>
	<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
</template>

<script setup>
    import {ref} from 'vue'
	const activeColor = ref('red')
    const fontSize = ref(30)
</script>
  • 在 React 中,依然是使用 {} 来绑定基本属性;但是,{} 中是通过 JavaScript 的对象值来实现的。

注意⚠️:

  • 在原生 CSS 中,一些属性是通过 - 连接的,如:background-color 等;
  • 在 Vue 和 React 中,都需要使用小驼峰的命名规则,如:backgroundColor 。
  • 很好理解,因为这是 JavaScript 中规定的,在 JavaScript 中变量的命名是不能使用 - 连接的;即使在 对象中,如果属性名需要使用 - 连接,也需要加上 ''
  • 示例:
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {
      // 组件数据
      state = {
        objStyle: {
          color: 'blue',
          fontSize: '18px'
        }
      }

      // 渲染内容 render 方法
      render() {
        const { objStyle } = this.state

        return (
          <div>
            { /* 绑定 style 属性 */}
            <h2 style={{ color: 'red', fontSize: 14 + 'px' }}>呵呵呵</h2>
            <h2 style={objStyle}>呵呵呵</h2>
          </div>
        )
      }
    }

    // 渲染组件
    app.render(<App />)
  </script>

</body>
</html>

3.4.3 JSX 绑定 class 类

  • 在 Vue 中,对于 class 类,是通过传递一个对象来动态切换 class
vue
<template>
	<div class="static" :class="{ active: isActive, 'text-danger': hasError }"></div>
</template>

<script setup>
    import {ref} from 'vue'
	const isActive = ref(true)
    const hasError = ref(false)
</script>
  • 在 Vue 中,对于 class 类,还可以传递一个数组来渲染多个 class
vue
<template>
	<div :class="[activeClass, errorClass]"></div>
</template>

<script setup>
    import {ref} from 'vue'
	const activeClass = ref('active')
    const errorClass = ref('text-danger')
</script>
  • 在 React 中,和 Vue 有点不同,需要使用 className 来完成 class 的绑定。

注意⚠️:在 React 中,如果想要实现类似 Vue 中对象动态切换 class 的那种效果,需要使用第三方库 classnames 。

  • 示例:class 属性的直接绑定
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {

      // 渲染内容 render 方法
      render() {
        return (
          <div>
            { /* class属性的绑定 */}
            <h2 className='abc'>哈哈哈</h2>
          </div>
        )
      }
    }

    // 渲染组件
    app.render(<App />)
  </script>

</body>
</html>
  • 示例:通过字符串的拼接
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <style>
    .abc {
      background-color: pink;
    }

    .active {
      font-size: 18px;
      font-weight: bold;
    }
  </style>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {
      // 组件数据
      state = {
       isActive: true,
      }

      // 渲染内容 render 方法
      render() {
        const { isActive } = this.state
        // class 绑定写法一:字符串的拼接
        const activeClassName = `abc cba ${isActive ? 'active' : ''}`

        return (
          <div>
            { /* class属性的绑定 */}     
            <h2 className={`abc cba ${isActive ? 'active' : ''}`}>哈哈哈</h2>
            <h2 className={activeClassName}>哈哈哈</h2>
          </div>
        )
      }
    }

    // 渲染组件
    app.render(<App />)
  </script>

</body>
</html>
  • 示例:通过数组的拼接
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <style>
    .abc {
      background-color: pink;
    }

    .active {
      font-size: 18px;
      font-weight: bold;
    }
  </style>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {
      // 组件数据
      state = {
        isActive: true,
      }

      // 渲染内容 render 方法
      render() {
        const {isActive } = this.state

        // class 绑定写法二:数组的拼接
        const classList = ['abc', 'cba']
        if (isActive) {
          classList.push('active')
        }

        return (
          <div>
            { /* class属性的绑定 */}
            <h2 className={classList.join(' ')}>哈哈哈</h2>
          </div>
        )
      }
    }

    // 渲染组件
    app.render(<App />)
  </script>

</body>
</html>

3.5 JSX 的事件绑定(⭐)

3.5.1 概述

  • 原生 JavaScript 中的事件监听有三种方式:

    • ① 直接在 HTML 中编写 JavaScript 代码(很少使用)。
    • ② 通过元素的 on 属性来监听事件。
    • ③ 通过 EventTarget 的 addEventListener 来监听事件(推荐)。
  • 示例:

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta content="IE=edge" http-equiv="X-UA-Compatible">
  <meta content="width=device-width, initial-scale=1.0" name="viewport">
  <title>Title</title>
</head>
<body>
  <!-- 直接在 HTML 中编写 JavaScript 代码(了解) -->
  <button onclick="console.log(`按钮1发生了点击`)">按钮1</button>
  <button class="btn2">按钮2</button>
  <button class="btn3">按钮3</button>

  <script>
    // 获取元素
    var btn2El = document.querySelector(".btn2")
    var btn3El = document.querySelector(".btn3")

    // onclick 属性
    btn2El.onclick = function () {
      console.log(`按钮2发生了点击`)
    }

    // addEventListener(推荐)
    btn3El.addEventListener('click', function () {
      console.log(`按钮3发生了点击`)
    })
  </script>

</body>
</html>
  • 在 React 中的事件监听和原生 JavaScript 的事件监听有稍许不同:

    • React 中的事件命名采用的是小驼峰的命名规则,如:onClickOnDbClick 等。
    • 我们可以通过 {} 插入一个事件处理函数,该事件处理函数会在事件发生的时候被执行。
  • 示例:

html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建一个组件
    class App extends React.Component {
      constructor() {
        super();
        this.state = {
          message: '你好,React'
        }
      }

      // 组件方法
      change = () => {
        this.setState({
          message: '你好,React18'
        })
      }

      // 渲染内容 render 方法
      render() {
        const {message} = this.state
        return (
          <div>
            <h2>{message}</h2>
            {/* React 中的`事件命名`采用的是`小驼峰`的命名规则,如:`onClick`、`OnDbClick` 等。 */}
            {/* 我们可以通过 `{}` 插入一个事件处理函数,该事件处理函数会在事件发生的时候被执行。 */}
            <button onClick={this.change.bind(this)}>改变内容</button>
          </div>
        )
      }
    }

    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))
    // 渲染组件
    app.render(<App />)
  </script>

</body>
</html>

3.5.2 this 绑定的最佳实践

3.5.2.1 this 绑定方式一

  • 我们可以通过在构造器调用方法处通过手动通过 bind 绑定 this 来解决在类中的方法中,this 丢失的问题。

  • 示例:

html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {
      constructor() {
        super();
        this.state = {
          count: 0
        }
        
        // 在构造器中手动对函数中的 this 进行绑定
        this.btn1Click = this.btn1Click.bind(this)
      }

      btnClick() {
        this.setState({
          count: this.state.count + 1
        })
      }

      render() {
        const {count} = this.state
        return (
            <div>
              <h2>当前计数为:{count}</h2>
              <button onClick={this.btnClick}>点我</button>
            </div>
        )
      }
    }

    // 渲染组件
    app.render(<App/>)
  </script>

</body>
</html>
  • 示例:
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {
      constructor() {
        super();
        this.state = {
          count: 0
        }
      }

      btnClick() {
        this.setState({
          count: this.state.count + 1
        })
      }


      render() {
        const {count} = this.state
        return (
            <div>
              <h2>当前计数为:{count}</h2>
              {/* 之所以也可以成功,是因为 render 方法中是可以访问 this 的,而我们就可以手动的通过 bind 来对 btnClick 方法进行 this 绑定了*/}
              <button onClick={this.btnClick.bind(this)}>点我</button>
            </div>
        )
      }
    }

    // 渲染组件
    app.render(<App/>)
  </script>

</body>
</html>

3.5.2.2 this 绑定方式二

  • 在 ES6+ 中,类中可以写 class fields ,即类属性

image-20231214163139007

  • 可能不是很明白,我们可以看下面的代码:
js
'use strict';

class Animal {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    sayName = function(){console.log(this.name);};

    sayAge = () =>{ console.log(this.age);}
}

let tom = new Animal('tom',19);
let [sayName, sayAge] = [tom.sayName, tom.sayAge];
sayAge();   // 正常输出 19
sayName();  // this 为 undefined,会报错
  • 箭头函数不会创建自己的 this,而是会继承外层作用域的 this 。当我们将 tom.sayName 和 tom.sayAge 赋值给 sayName 和sayAge 变量时,这 sayAge 仍然保留了对 tom 实例的引用。
  • 当然,更加具体的解释在这里:

image-20231214163824731

  • 所以,我们可以在 React 中,通过 ES6 的 class fields 语法来解决 this 绑定问题。

  • 示例:

html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {
      
      state = {
        count: 0
      }

      btnClick2 = () => {
        this.setState({
          count: this.state.count + 1
        })
      }

      render() {
        const {count} = this.state
        return (
            <div>
              <h2>当前计数为:{count}</h2>
              {/* ES6 的 class fields */}
              <button onClick={this.btnClick2}>点我</button>
            </div>
        )
      }
    }

    // 渲染组件
    app.render(<App/>)
  </script>

</body>
</html>

3.5.2.3 this 绑定方式三

  • 我们也可以通过在事件监听中传入箭头函数的方式来解决 this 绑定问题(强烈推荐)。

注意⚠️:() => {} 箭头函数也是表达式,所以 {} 中就可以插入箭头函数

  • 示例:在事件监听中传入箭头函数
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {

      state = {
        count: 0
      }

      render() {
        const {count} = this.state
        return (
            <div>
              <h2>当前计数为:{count}</h2>
              {/* 直接传入一个箭头函数 */}
              {/* 我们会发现当点击按钮的时候,就触发了 alert("abc") */}
              {/*
              	原理类似:
              	const click = () => alert("abc")
              	click() 
              */}
              <button onClick={() => alert("abc")}>点我</button>
            </div>
        )
      }
    }

    // 渲染组件
    app.render(<App/>)
  </script>

</body>
</html>
  • 示例:在事件监听中传入箭头函数,并通过 this 调用类中的方法
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {

      state = {
        count: 0
      }
      
      btnClick3() {
        this.setState({
          count: this.state.count + 1
        })
      }

      render() {
        const {count} = this.state
        return (
            <div>
              <h2>当前计数为:{count}</h2>
              {/* 直接传入一个箭头函数 */}
              {/* 
              	 原理类似
                 const click = () => this.btnClick3()
                 click() 
                 
                 当按钮被点击的时候,会触发 click() ,进而会执行 this.btnClick3()
                 又因为 () => this.btnClick3() 中的箭头函数中没有 this 的,那么 this 就是当前实例对象(因为 render 函数中是有 this 的)。
                 而当前实例对象身上是有 btnClick3() 方法的,而 btnClick3 是这样定义的 btnClick3() {},
                 所以,this 没有丢失。
              */}
              <button onClick={() => this.btnClick3()}>点我</button>
            </div>
        )
      }
    }

    // 渲染组件
    app.render(<App />)
  </script>

</body>
</html>

3.5.3 事件参数传递

3.5.3.1 event 参数的传递

  • 对于原生 JavaScript 而言,我们是在回调函数中,通过参数 event 来得到事件对象的,如:
js
boxEl.addEventListener('click',function(event){
     // event 就是事件对象                  
})
  • 在 React 中,我们也可以通过在类方法中通过参数 event 来得到事件对象
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {
      // 组件数据
      state = {
        message: '你好,React'
      }

      btn1Click = (event) => {
        console.log('btn1Click', event, this)
      }

      render() {
        return (
          <div>
            {/* event 参数的传递 */}
            <button onClick={this.btn1Click}>按钮1</button>
          </div>
        )
      }
    }

    // 渲染组件
    app.render(<App />)
  </script>

</body>
</html>
  • 但是,对于在事件监听中传入箭头函数的方式而言,需要手动传递 event 事件对象参数:
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {
      // 组件数据
      state = {
        message: '你好,React'
      }

      btn2Click(event) {
        console.log('btn2Click', event, this)
      }

      render() {
        return (
          <div>
            {/* event 参数的传递 */}
            <button onClick={(event) => this.btn2Click(event)}>按钮2</button>
          </div>
        )
      }
    }

    // 渲染组件
    app.render(<App />)
  </script>

</body>
</html>

3.5.3.2 额外参数的传递

  • 有的时候,我们除了要在类方法中获取 event 事件对象参数之外,还需要传递额外的参数;此时,就推荐在事件监听中传入箭头函数的方式了。

注意⚠️:这种方式传递参数的个数位置是很直观的的,而其他两种方式不是很直观(因为 event 参数最后一个,增加了记忆成本)。

  • 示例:
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {
      // 组件数据
      state = {
        message: '你好,React'
      }

      btn2Click(event, name, age) {
        console.log('btn2Click', event, this, name, age)
      }

      render() {
        return (
          <div>
            {/* 额外参数的传递 */}
            {/* 这种方式传递参数的`个数`和`位置`是很直观的的,指的是 this.btn2Click(event, '张三', 18) */}
            <button onClick={(event) => this.btn2Click(event, '张三', 18)}>按钮3</button>
          </div>
        )
      }
    }

    // 渲染组件
    app.render(<App />)
  </script>

</body>
</html>

3.5.4 案例

  • 需求:电影选中,颜色高亮。

实现思路:

  • ① 在 state 中保存当前的选中数组的索引值,初始为 0 ,即第一个数组元素。
  • ② 在 li 元素中通过 onClick 事件,获取当前选中的索引值,并将当前的索引值修改到 state 对应的变量中。
  • ③ 在 li 元素的 className 属性中根据 当前的索引值 = 选中的索引值,来动态添加或删除 active 类。
  • 示例:
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <style>
    .active {
      color: red;
    }
  </style>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {
      // 组件数据
      state = {
        movies: ['星际穿越', '大话西游', '梦幻西游', '诛仙'], // 电影列表数据
        currentIndex: 0 // 当前的索引
      }

      // 改变当前选中的索引
      changeCurrentIndex(index) {
        this.setState({
          currentIndex: index
        })
      }

      render() {
        const {movies, currentIndex} = this.state
        return (
            <div>
              <h2>电影列表</h2>
              <ul>
                {
                  movies.map((movie, index) => {
                    return (
                        <li key={index} className={currentIndex === index ? 'active' : ''}
                            onClick={() => this.changeCurrentIndex(index)}>
                          {movie}
                        </li>)
                  })
                }
              </ul>
            </div>
        )
      }
    }

    // 渲染组件
    app.render(<App/>)
  </script>

</body>
</html>

3.6 JSX 的条件渲染(⭐)

3.6.1 概述

  • 在 Vue 中,我们会使用 v-ifv-elsev-show 等指令进行条件渲染;但是,React 倡导的是 all in js ;那么,在原生 JavaScript 怎么玩,在 React 中也怎么玩。

条件渲染:我们可以创建不同的组件来封装各种我们需要的行为。然后,依据应用的不同状态,只渲染对应状态下的部分内容。

3.6.2 if 条件判断

  • if 条件判断语句,适合逻辑较多的情况;原理很简单,就是根据判断当前的状态,然后在 render 方法中返回(return)不同的 jsx (虚拟 DOM)组件

  • 示例:

html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {
      // 组件数据
      state = {
        message: '你好,React',
        isReady: true
      }

      render() {
        const { isReady } = this.state
        if (isReady) {
          return (
            <div>
              <h2>已经准备好了</h2>
            </div>
          )
        } else {
          return (
            <h2>稍等,还没准备好</h2>
          )
        }

      }
    }

    // 渲染组件
    app.render(<App />)
  </script>

</body>
</html>

3.6.3 元素变量

  • 我们可以通过变量来存储元素,这样可以帮助我们有条件的渲染组件的一部分,而其他的渲染部分并不会因此而改变。

注意⚠️:元素变量是 if 条件判断的一种变种而已。

  • 示例:
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {
      // 组件数据
      state = {
        message: '你好,React',
        isReady: true
      }

      render() {
        const { isReady } = this.state
        let showElement
        if (isReady) {
          showElement = (
            <div>
              <h2>已经准备好了</h2>
            </div>
          )
        } else {
          showElement = (
            <h2>稍等,还没准备好</h2>
          )
        }
        return (
          <div>
            { /* 条件判断语句:根据条件给变量赋值不同的内容,适合逻辑较多的情况~ */}
            {showElement}
          </div>
        )

      }
    }

    // 渲染组件
    app.render(<App />)
  </script>

</body>
</html>

3.6.4 三元运算符

  • 三元运算符逻辑比较简单的情况;虽然也可以用于较为复杂的表达式,但是看起来不够直观。

  • 示例:

html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {
      // 组件数据
      state = {
        message: '你好,React',
        isReady: true
      }


      render() {
        const { isReady } = this.state
        return (
          <div>
            {/* 三元运算符:适合逻辑比较简单的情况~ */}
            {
              isReady ? (<h2>已经准备好了</h2>) : (<h2>稍等,还没准备好</h2>)
            }
          </div>
        )

      }
    }

    // 渲染组件
    app.render(<App />)
  </script>

</body>
</html>

3.6.5 短路 &&

  • 我们知道,在原生 JavaScript 中, true && expression 总是返回 expression ;而 false && expression 总是返回 false

  • 同理,如果条件true,在 React 中,&& 右侧的元素被渲染;如果条件false,React 就不会渲染。

  • 示例:

html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {
      // 组件数据
      state = {
        message: '你好,React',
        friend: {
          name: '张三',
          age: 23
        }
      }

      render() {
        const { friend } = this.state
        return (
          <div>
            {/* && 逻辑与运算,当某一个值有可能为 null 或 undefined */}
            {
              friend && (<div>姓名:{friend.name} ,年龄:{friend.age}</div>)
            }
          </div>
        )

      }
    }

    // 渲染组件
    app.render(<App />)
  </script>

</body>
</html>

3.6.6 可选链 ?.

  • 可选链运算符(?.)允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。?. 运算符的功能类似于 . 链式运算符,不同之处在于,在引用为空 (nullish ) (null 或者 undefined) 的情况下不会引起错误,该表达式短路返回值是 undefined。与函数调用一起使用时,如果给定的函数不存在,则返回 undefined

  • 当尝试访问可能不存在的对象属性时,可选链运算符将会使表达式更短、更简明。在探索一个对象的内容时,如果不能确定哪些属性必定存在,可选链运算符也是很有帮助的。

  • 在 React 中,JSX 中的变量如果是 undefined,React 就不会渲染。

  • 示例:

html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {
      // 组件数据
      state = {
        message: '你好,React',
        friend: null
      }

      render() {
        const { friend } = this.state
        return (
          <div>
            {/* 可选链 ?. ,当某一个值有可能为 null 或 undefined */}
            {
              (<div>姓名:{friend?.name} ,年龄:{friend?.age}</div>)
            }
          </div>
        )

      }
    }

    // 渲染组件
    app.render(<App />)
  </script>

</body>
</html>

3.6.7 案例

  • 需求:通过点击按钮,模拟出 Vue 中的 v-if 效果。

  • 示例:
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {
      // 组件数据
      state = {
        message: '你好,React',
        isShow: true
      }

      changeShow() {
        const {isShow} = this.state
        this.setState({
          isShow: !isShow
        })
      }

      render() {
        const {message, isShow} = this.state
        return (
            <div>
              <button onClick={() => this.changeShow()}>切换</button>
              {/* 逻辑&& */}
              {isShow && (<h2>{message}</h2>)}
            </div>
        )

      }
    }

    // 渲染组件
    app.render(<App/>)
  </script>

</body>
</html>

3.7 JSX 本质(⭐)

  • 其实,JSX 仅仅只是 createElement(type, config, children) 函数的语法糖;换言之,所有的 JSX 都会被转换为 createElement(type, config, children) 函数调用。

注意⚠️:在 React 18 之前,是使用 React.createElement(component, props, ...children) ;但是,目前该 API 已经过时。

  • 其中,type 参数表示的是当前 ReactElement 的类型
    • 如果是标签元素,则使用字符串来表示,如:"div"
    • 如果是组件元素,则直接使用组件的名称,如:Demo

image-20231215151735720

  • 其中,config 参数是用来存储 JSX 中的属性
    • 都是以对象的形式存储的。

image-20231215151815983

  • 其中,children 参数是用来表示标签的内容,以 children 数组的方式来存储的。

image-20231215151916702

3.8 虚拟 DOM 的创建过程

  • 我们知道,可以通过 createElement(type, config, children) 的方式手动创建 React 元素,它可以作为 JSX 的替代方案。

image-20231215152816336

  • 其实,React 就是通过 createElement 创建出来的对象,组成了一个 JavaScript 对象树,并且这个 JavaScript 对象树就是虚拟 DOM(Virtual DOM)。
  • 以下面的代码为例:
html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {
      // 组件数据
      state = {
        message: '你好,React',
        isShow: true
      }

      changeShow() {
        const {isShow} = this.state
        this.setState({
          isShow: !isShow
        })
      }

      render() {
        const {message} = this.state

        const result = (
            <div>
              <button onClick={() => this.changeShow()}>切换</button>
              <h2>{message}</h2>)
            </div>
        )

        console.log('@@@',result)

        return result

      }
    }

    // 渲染组件
    app.render(<App/>)
  </script>

</body>
</html>
  • 我们来看下,这个虚拟 DOM 到底怎么形成的:

image-20231215153549102

  • 如何证明?我们可以将虚拟 DOM 打印出来:

image-20231215153652797

  • 其实,查看 createElement 函数的源码, 我们也知道:createElement 函数返回的就是一个 JavaScript 对象。
js
  var ReactElement = function (type, key, ref, self, source, owner, props) {
    var element = {
      $$typeof: REACT_ELEMENT_TYPE,
      type: type,
      key: key,
      ref: ref,
      props: props,
      _owner: owner
    };
    // 其余略
    return element;
  };

  function createElement(type, config, children) {
    // 其余略
    return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
  }
  • 所以,最终的开发模式就是这样:我们只需要写 JSX ,其余的就交给 React 了。

image-20231215154859498

3.9 声明式编程(⭐)

  • 虚拟 DOM 的出现将开发者的编程模式命令式编程转变到声明式编程
  • 虚拟 DOM 是一种编程概念。在这个概念里, UI 以一种理想化的,或者说“虚拟的”表现形式被保存于内存中,并通过如 ReactDOM 等类库使之与“真实的” DOM 同步。这一过程叫做协调
  • 这种编程的方式赋予了 React 声明式的 API :
    • 我们告诉 React 希望让 UI 是什么状态,React 就确保 DOM 匹配该状态。
    • 这使我们可以从属性操作事件处理手动 DOM 更新这些在构建应用程序时必要的操作中解放出来。

3.10 购物车案例(⭐)

  • 需求:展示购物车。

  • 要求:

    • ① 在界面上以表格的形式展示书籍的数据。
    • ② 在底部显示书籍的总价格
    • ③ 点击 +- 就增加书籍的数量或减少书籍的数量;但是,如果书籍的数量为 1 ,就不能继续减少。
    • ④ 点击移除按钮,就将当前书籍移除。
    • ⑤ 如果所有书籍移除了,界面显示暂无数据
  • 示例:

html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>Title</title>
  <style>
    table {
      border-collapse: collapse;
      text-align: center;
    }

    table th, td {
      border: 1px solid black;
      padding: 8px 16px;
    }

    table thead {
      background-color: #f5f5f5;
    }

    .active {
      background-color: pink;
    }
  </style>
  <!-- react:React 的核心 -->
  <script src='./js/react18.development.js'></script>
  <!-- React-DOM:React 渲染在不同平台上所需要的核心代码 -->
  <script src='./js/react-dom18.development.js'></script>
  <!-- babel:将 jsx 转换为 React 代码的工具 -->
  <script src='./js/babel.js'></script>
</head>
<body>

  <!-- 准备一个容器 -->
  <div class='app'></div>

  <script type='text/babel'>
    // 创建根元素
    const app = ReactDOM.createRoot(document.querySelector('.app'))

    // 创建一个组件
    class App extends React.Component {

      state = {
        books: [
          {
            id: 1,
            name: '《算法导论》',
            date: '2006-9',
            price: 85.00,
            count: 1
          },
          {
            id: 2,
            name: '《UNIX编程艺术》',
            date: '2006-2',
            price: 59.00,
            count: 1
          },
          {
            id: 3,
            name: '《编程珠玑》',
            date: '2008-10',
            price: 39.00,
            count: 1
          },
          {
            id: 4,
            name: '《代码大全》',
            date: '2006-3',
            price: 128.00,
            count: 1
          },
          {
            id: 5,
            name: '《你不知道的JavaScript》',
            date: '2014-8',
            price: 88.00,
            count: 1
          },
        ]
      }

      // 改变购物车中书籍的数量,如果不传递 num ,就是新增;否则,传递 -1 就是减少
      changeCount(id, num = 1) {
        const {books} = this.state

        if (id) {
          const newBooks = books.map(book => {
            if (book.id === id) {
              book.count += num
            }
            return book
          })
          this.setState({
            books: newBooks
          })
        }
      }

      // 删除购物车中的书籍
      delete(id) {
        const {books} = this.state

        if (id) {
          const newBooks = books.filter(book => book.id !== id)
          this.setState({
            books: newBooks
          })
        }
      }

      // 当购物车中的书籍数据不为空的时候的渲染函数
      renderBookList() {
        const {books} = this.state
        const totalPrice = books.map(item => item.price * item.count).reduce((prev, next) => prev + next, 0)
        return (<div>
          <table>
            <thead>
              <tr>
                <th>序号</th>
                <th>书籍名称</th>
                <th>出版日期</th>
                <th>价格</th>
                <th>购买数量</th>
                <th>操作</th>
              </tr>
            </thead>
            <tbody>
              {
                  books && books.map((book, index) => {
                    return (
                        <tr key={index}>
                          <td>{index}</td>
                          <td>{book.name}</td>
                          <td>{book.date}</td>
                          <td>{"¥" + book.price.toFixed(2)}</td>
                          <td>
                            <button disabled={book.count <= 1} onClick={() => this.changeCount(book.id, -1)}>-</button>
                            {book.count}
                            <button onClick={() => this.changeCount(book.id)}>+</button>
                          </td>
                          <td>
                            <button onClick={() => this.delete(book.id)}>移除</button>
                          </td>
                        </tr>
                    )
                  })
              }
            </tbody>
            <tfoot>
              <tr>
                <td colSpan="6">总价格:{"¥" + totalPrice.toFixed(2)}</td>
              </tr>
            </tfoot>
          </table>
        </div>)
      }

      // 当购物车中的书籍数据为空的时候的渲染函数
      renderBookEmpty() {
        return (<div>暂无数据</div>)
      }

      render() {
        const {books} = this.state

        return books.length ? this.renderBookList() : this.renderBookEmpty()
      }
    }

    // 渲染组件
    app.render(
        <App/>
    )
  </script>

</body>
</html>

Released under the MIT License.