Skip to content

一. 认识jsx语法


1. 认识jsx

jsx
// 1. 定义根组件
const el = <div>hello world</div>
// 2. 渲染组件
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(el)
  • 这段el变量的声明右侧赋值的标签语法是什么呢?
    • 它不是一段字符串(因为没有使用引号包裹)
    • 它看起来是一段HTML元素,但是我们能在js中直接给一个变量赋值html吗?
    • 其实是不可以的,如果我们将type="text/babel"去除掉,那么就会出现语法错误
    • 它到底是什么呢?其实它是一段jsx的语法
  • jsx是什么?
    • jsx是一种**js的语法扩展**(eXtension),也在很多地方称之为JavaScript XML,因为看起就是一段XML语法
    • 它用于描述我们的UI界面,并且其完成可以和js融合在一起使用
    • 它不同于Vue中的模块语法,你不需要专门学习模块语法中的一些指令(比如v-for、v-if、v-else、v-bind

2. 为什么React选择了jsx

  • React认为渲染逻辑本质上与其他UI逻辑存在内在耦合
    • 比如UI需要绑定事件(buttona原生等等)
    • 比如UI中需要展示数据状态
    • 比如在某些状态发生改变时,又需要改变UI
  • 他们之间是密不可分,所以React没有将标记分离到不同的文件中,而是将它们组合到了一起,这个地方就是组件Component
    • 当然,后面我们还是会继续学习更多组件相关的东西
  • 在这里,我们只需要知道,jsx其实是嵌入到js中的一种结构语法
  • jsx的书写规范:
    • jsx的顶层只能有一个根元素,所以我们很多时候会在外层包裹一个div元素(或者使用后面我们学习的Fragment
    • 为了方便阅读,我们通常在jsx的外层包裹一个小括号(),这样可以方便阅读,并且jsx可以进行换行书写
    • jsx中的标签可以是单标签,也可以是双标签
      • 注意:如果是单标签,必须以/>结尾

二. jsx的基本使用


1. jsx的使用

  • jsx中的注释

    • jsx文件中,直接使用ctrl + /,自动会生成对应的注释
    jsx
    <div>
      {/* 这是一个jsx的注释 */}
      <h2>当前计数:{ count }</h2>
      <button onClick={ this.increment }>+1</button>
      <button onClick={ this.decrement }>-1</button>
    </div>
  • jsx嵌入变量作为子元素

    • 情况一:当变量是Number、String、Array类型时,可以直接显示
    • 情况二:当变量是null、undefined、Boolean类型时,内容为空
      • 如果希望显示null、undefined、Boolean,那么需要转成字符串才能正确显示
      • 转换的方式有很多,比如toString方法、和空字符串拼接,String()等方式
    • 情况三:Object对象类型不能作为子元素进行显示Objects not valid as a React child
  • jsx嵌入表达式

    • 运算表达式
    • 三元运算符
    • 执行一个函数
    jsx
    render() {
      // 1.插入标识符
      const { message, names, counter } = this.state
      const { aaa, bbb, ccc } = this.state
      const { friend } = this.state
    
      // 2.对内容进行运算后显示(插入表示)
      const { firstName, lastName } = this.state
      const fullName = firstName + " " + lastName
      const { age } = this.state
      const ageText = age >= 18 ? "成年人": "未成年人"
      const liEls = this.state.movies.map(movie => <li>{movie}</li>)
    
      // 3.返回jsx的内容
      return (
        <div>
          {/* 1.Number/String/Array直接显示出来 */}
          <h2>{counter}</h2>
          <h2>{message}</h2>
          <h2>{names}</h2>
    
          {/* 2.undefined/null/Boolean */}
          <h2>{String(aaa)}</h2>
          <h2>{bbb + ""}</h2>
          <h2>{ccc.toString()}</h2>
    
          {/* 3.Object类型不能作为子元素进行显示*/}
          <h2>{friend.name}</h2>
          <h2>{Object.keys(friend)[0]}</h2>
    
          {/* 4.可以插入对应的表达式*/}
          <h2>{10 + 20}</h2>
          <h2>{firstName + " " + lastName}</h2>
          <h2>{fullName}</h2>
    
          {/* 5.可以插入三元运算符*/}
          <h2>{ageText}</h2>
          <h2>{age >= 18 ? "成年人": "未成年人"}</h2>
    
          {/* 6.可以调用方法获取结果*/}
          <ul>{liEls}</ul>
          <ul>{this.state.movies.map(movie => <li>{movie}</li>)}</ul>
          <ul>{this.getMovieEls()}</ul>
        </div>
      )
    }
    
    getMovieEls() {
      const liEls = this.state.movies.map(movie => <li>{movie}</li>)
      return liEls
    }

2.jsx的使用

  • jsx绑定属性

    • 比如元素都会有title属性
    • 比如img元素会有src属性
    • 比如a元素会有href属性
    • 比如元素可能需要绑定class
    • 比如原生使用内联样式style
    jsx
    class APP extends React.Component {
      constructor() {
        super()
        this.state = {
          count: 0,
          title: '动态绑定的标题',
          imgURL: "https://ts1.cn.mm.bing.net/th/id/R-C.95bc299c3f1f0e69b9eb1d0772b14a98",
          isActive: true,
          classList: ['aaa', 'bbb', 'ccc'],
          c_red: {
            color: 'red'
          }
        }
      }
    
      render() {
        const {  title, imgURL, isActive, classList, c_red } = this.state
        const className = `aaa bbb ${isActive ? 'active' : ''}`
    
        return (
          <div>
            <h2 title="固定的标题">固定的标题</h2>
            <h2 title={ title }>动态绑定的标题</h2>
            {/* <img src={ imgURL } alt="" /> */}
    
            <h2 class="aaa">这种在jsx写class, babel转化之后, 虽然也可以正常显示, 但是react-dom会抛出一个j, 不介意使用</h2>
            <h2 className={ className }>推荐使用className</h2>
            <h2 className={ classList }>大括号中的标识符会被调用其toString方法</h2> 
            <h2 className={ `${curIndex === index ? 'active' : ''} item` } ></h2>
    
            <h2 style={ c_red }>h2元素</h2>
          </div>
        )
      }
    }

三. jsx的事件绑定


1.React事件绑定

  • 如果原生DOM原生有一个监听事件,我们可以如何操作呢?
    • 方式一:获取DOM原生,添加监听事件
    • 方式二:在HTML原生中,直接绑定onclick
  • React中是如何操作呢?我们来实现一下React中的事件监听,这里主要有两点不同
    • React 事件的命名采用小驼峰式(camelCase),而不是纯小写(原生事件纯小写)
    • 我们需要通过{}传入一个事件处理函数,这个函数会在事件发生时被执行

2.this的绑定问题

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

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

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

    • 方案一:bindbtnClick显示绑定this
    • 方案二:使用ES6 class fields语法
    • 方案三:事件监听时传入箭头函数
    jsx
    class App extends React.Component {
      constructor() {
        super()
        this.changeText = this.changeText.bind(this) // 写法二
      }
    
      changeText() {
        console.log(this)
      }
    
      // 写法三:  class 的 field,ES13中,新增了定义class类中成员字段field的其他方式
      changeText = () => {
        console.log(this)
      }
    
      render() {
        return (
          <div>
            {/*  写法一 */}
            <button onClick={ this.changeText.bind(this) }>改变文本</button>
            {/*  写法四 */}
            <button onClick={ () => this.changeText() }>改变文本</button>
          </div>
        )
      }
    }

3.事件参数传递

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

  • 情况一:获取event对象

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

    • 有更多参数时,我们最好的方式就是传入一个箭头函数,主动执行的事件函数,并且传入相关的其他参数
    jsx
    render() {
      return (
        <div>
          <button onClick={e => this.btnClick(e, 'later', 18)}></button>
        </div>
      )
    }

四. jsx的条件渲染


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

    • vue中,我们会通过指令来控制:比如v-if、v-show
    • React中,所有的条件判断都和普通的js代码一致
  • 常见的条件渲染的方式有哪些呢?

  • 方式一:条件判断语句

    • 适合逻辑较多的情况
  • 方式二:三元运算符

    • 适合逻辑比较简单
  • 方式三:与运算符&&

    • 适合如果条件成立,渲染某一个组件;如果条件不成立,什么内容也不渲染
  • v-show的效果

    • 主要是控制display属性是否为none
    jsx
    class App extends React.Component {
      constructor() {
        super()
        this.state = {
          message: "Hello World",
          isReady: false,
          friend: undefined,
          isShow: true
        }
      }
    
      changeShow() {
        this.setState({ isShow: !this.state.isShow })
      }
    
      render() {
        const { isReady, friend, isShow } = 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.方式三: &&逻辑与运算 */}
            {/* 场景: 当某一个值, 有可能为undefined时, 使用&&进行条件判断 */}
            <div>{ friend && <div>{friend.name + " " + friend.desc}</div> }</div>
    
            {/* v-show的效果 */}
            <button onClick={() => this.changeShow()}>切换</button>
            { isShow && <h2>{message}</h2> }
            <h2 style={{display: isShow ? 'block': 'none'}}>哈哈哈哈</h2>
          </div>
        )
      }
    }

五. jsx的列表渲染


1.React列表渲染

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

    • 比如歌曲、歌手、排行榜列表的数据
    • 比如商品、购物车、评论列表的数据
    • 比如好友消息、动态、联系人列表的数据
  • React中并没有像Vue模块语法中的v-for指令,而且需要我们通过js代码的方式组织数据,转成jsx

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

    • React中,展示列表最多的方式就是使用数组的map高阶函数
  • 很多时候我们在展示一个数组中的数据之前,需要先对它进行一些处理:

    • 比如过滤掉一些内容:filter函数
    • 比如截取数组中的一部分内容:slice函数
    jsx
    class App extends React.Component {
      constructor() {
        super()
        this.state = {
          students: [
            { id: 111, name: "why", score: 199 },
            { id: 112, name: "kobe", score: 98 },
            { id: 113, name: "james", score: 199 },
            { id: 114, name: "curry", score: 188 },
          ]
        }
      }
    
      render() {
        const { students } = this.state
    
        // 分数大于100的学生进行展示
        const filterStudents = students.filter(item => {
          return item.score > 100
        })
    
        // 分数大于100, 只展示两个人的信息
        // slice(start, end): [start, end)
        const sliceStudents = filterStudents.slice(0, 2)
    
        return (
          <div>
            <h2>学生列表数据</h2>
            <div className="list">
              {
                students.filter(item => 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.列表中的key

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

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

    • key主要的作用是为了提高diff算法时的效率(diff算法本质也是在减少dom更新,减少回流重绘等带来的性能消耗)

六. jsx的原理和本质


1.jsx的本质

  • 实际上,jsx 仅仅只是React.createElement(component, props, ...children)函数的语法糖
    • 所有的jsx最终都会被转换成React.createElement的函数调用 ,最终也就是创建ReactElement
  • createElement需要传递三个参数:
  • 参数一:type
    • 当前ReactElement的类型
    • 如果是标签元素,那么就使用字符串表示 “div
    • 如果是组件元素,那么就直接使用组件的名称
  • 参数二:config
    • 所有jsx中的属性都在config中以对象的属性和值的形式存储
    • 比如传入className作为元素的class
  • 参数三:children
    • 存放在标签中的内容,以children数组的方式进行存储
    • 当然,如果是多个元素呢?React内部有对它们进行处理,处理的源码在下方
image-20220903221456991

2.Babel官网查看

  • 我们知道默认jsx是通过babel帮我们进行语法转换的,所以我们之前写的jsx代码都需要依赖babel

  • 可以在babel的官网中快速查看转换的过程:https://babeljs.io/repl/#?presets=react

    • 对于render方法返回的jsx代码,通过babel会转化为React.createElement函数调用的代码,函数调用返回的js / React.creatElement对象,其内部有形成一个嵌套结构,其实就是虚拟DOM,最终React会通过ReactDOM.render()调用将其转化成真实dom进行渲染

      js
      	ReactNative => render => 原生控件
      	SSR => render => 字符
      • VuetemplateVue内部自己解析的,而不是依赖于React,如果在Vue中使用jsx,也是依赖babel进行解析的
    • 可以将babel转化后的React代码,直接放在render函数中,直接被React解析的,效果是一样的

    • /*__PURE__*/是代表纯函数的意思。当没有引用时,会被tree shaking自动删除掉的

image-20220903221932616

3.直接编写jsx代码

  • 我们自己来编写React.createElement代码:
    • 我们就没有通过jsx来书写了,界面依然是可以正常的渲染

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

      • 所以,type="text/babel"可以被我们删除掉了

      • 所以,<script src="../react/babel.min.js"></script>可以被我们删除掉了

        image-20220904175132529

4.虚拟DOM的创建过程

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

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

    • 原因是React利用ReactElement对象组成了一个js的对象树
    • js的对象树就是虚拟DOM(Virtual DOM)
  • 如何查看ReactElement的树结构呢?

    • 我们可以将之前的jsx返回结果进行打印

      image-20220904215149566
  • ReactElement最终形成的树结构就是Virtual DOM

5.jsx – 虚拟DOM – 真实DOM

image-20220904215913212

6.声明式编程

  • 虚拟DOM帮助我们从命令式编程转到了声明式编程的模式
  • React官方的说法:Virtual DOM 是一种编程理念
    • 在这个理念中,UI以一种理想化或者说虚拟化的方式保存在内存中,并且它是一个相对简单的js对象
    • 我们可以通过ReactDOM.render让 虚拟DOM 和 真实DOM同步起来,这个过程中叫做协调Reconciliation
      • 就是jsx代码渲染到真实dom的过程
  • 这种编程的方式赋予了React声明式的API
    • 你只需要告诉React希望让UI是什么状态
    • React来确保DOM和这些状态是匹配的
    • 你不需要直接进行DOM操作,就可以从手动更改DOM、属性操作、事件处理中解放出来
  • 关于虚拟DOM的一些其他内容,在后续的学习中还会再次讲到

7. 阶段性案例练习

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

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

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

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

    image-20220904220327901
  • 代码如下:

    jsx
    <!DOCTYPE html>
    <html lang="zh-CN">
    <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>
    <style>
      :root {
        padding: 10px;
      }
    
      td, th {
        padding: 6px 10px;
        border: 1px solid #ccc
      }
    
      table {
        border-collapse: collapse;
        text-align: center;
      }
    
      thead {
        background-color: #eee;
      }
    
      tfoot th {
        border: none;
        font-size: 20px;
        font-weight: bold;
      }
    </style>
    </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">
    
      class APP extends React.Component {
        constructor() {
          super()
          this.state = {
            titles: [ '序号', '书籍名称', '出版日期', '价格', '购买数量', '操作' ],
            books: [
              { id: 1, name: '《算法导论》', publicDate: '2006-9', price: 85, count: 1 },
              { id: 2, name: '《UNIX编程艺术》', publicDate: '2006-2', price: 59, count: 1 },
              { id: 3, name: '《编程珠玑》', publicDate: '2008-10', price: 39, count: 1 },
              { id: 4, name: '《代码大全》', publicDate: '2006-3', price: 128, count: 1 }
            ],
            totalPrice: 0
          }
        }
    
        changeCount(index, num = 1) {
          const newBooks = [...this.state.books]
          newBooks[index].count += num
          this.setState({
            books: newBooks
          })
        }
    
        getTotalPrice() {
          return this.state.books.reduce((preValue, curValue) => {
            return preValue + curValue.price * curValue.count
          }, 0)
        }
    
        removeItem(index) {
          const newBooks = [...this.state.books]
          newBooks.splice(index, 1)
          this.setState({
            books: newBooks
          })
        }
    
        renderBooks() {
          const { titles, books } = this.state
          return (
            <div>
              <table>
                <thead>
                  <tr>
                    { titles.map((item) => <th key={ item }>{ item }</th>) }
                  </tr>  
                </thead>
                <tbody>
                  {
                    books.map((book, index) => (
                      <tr key={ book.name }>
                        {
                          Object.keys(book).map(key => (
                            <th key={ key }>
                              { key == 'count' ? <button disabled={ book['count'] == 0 } onClick={ () => this.changeCount(index, -1) }>-</button> : '' }
                              { key == 'id' ? index + 1 : book[key] }
                              { key == 'count' ? <button onClick={ () => this.changeCount(index) }>+</button> : '' }
                            </th>
                          ))
                        }
                        <th>
                          <button onClick={ () => this.removeItem(index) }>删除</button>
                        </th>
                      </tr>
                    ))
                  }
                </tbody>
                <tfoot>
                  <tr>
                    <th>总价格:¥{ this.getTotalPrice() }</th>
                  </tr>
                </tfoot>
              </table>
            </div>
          )
        }
    
        renderBookEmpty() {
          return <div>购物车空空如也~</div>
        }
    
        render() {
          return (
            this.state.books.length ? this.renderBooks() : this.renderBookEmpty()
          )
        }
      }
    
      const rootEl = document.querySelector('#root')
      const root = ReactDOM.createRoot(rootEl)
      root.render(<APP/>)
    </script>
    </body>
    </html>

8. React必须在作用域内

  • 由于 JSX 会编译为 React.createElement 调用形式,所以 React 库也必须包含在 JSX 代码作用域内

  • 例如,在如下代码中,虽然 ReactCustomButton 并没有被直接使用,但还是需要导入:

    jsx
    import React from 'react'
    import CustomButton from './CustomButton'
    
    function WarningButton() {
      // return React.createElement(CustomButton, {color: 'red'}, null);
      return <CustomButton color="red" />
    }
  • 如果你不使用 JavaScript 打包工具而是直接通过 <script> 标签加载 React,则必须将 React 挂载到全局变量中

Released under the MIT License.