5
头图

快来加入我们吧!

"小和山的菜鸟们",为前端开发者提供技术相关资讯以及系列基础文章。为更好的用户体验,请您移至我们官网小和山的菜鸟们 ( https://xhs-rookies.com/ ) 进行学习,及时获取最新文章。

"Code tailor" ,如果您对我们文章感兴趣、或是想提一些建议,微信关注 “小和山的菜鸟们” 公众号,与我们取的联系,您也可以在微信上观看我们的文章。每一个建议或是赞同都是对我们极大的鼓励!

前言

这节我们将教你一个有趣的标签语法,他既不是字符串也不是 HTML,他被称为 JSX,是一个 JavaScript 的语法扩展,我们建议在 React 中配合使用 JSXJSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。

本文会向你介绍以下内容:

  • 认识 JSX 的语法
  • JSX 中嵌入表达式
  • 事件监听
  • 条件渲染
  • 列表渲染
  • JSX 原理解析
  • 案例练习

认识 JSX 的语法

JSX 是什么?

我们先来看一段代码:

const element = <h1>Hello, XHS-Rookies!</h1>

这段 element 变量的声明右侧赋值的标签语法是什么呢?

这个有趣的标签语法既不是字符串也不是 HTML

它被称为 JSX,是一个 JavaScript 的语法扩展。我们建议在 React 中配合使用 JSXJSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。JSX 可能会使人联想到模版语言,但它具有 JavaScript 的全部功能。

注意: 对于JSX来说,虽然是一种JavaScript语法扩展,但是你能发现其无法直接使用在HTML中,需要借助babel的转换,转换后会自动帮我解析成想要的样式。

为什么使用 JSX?

React 认为渲染逻辑本质上与其他 UI 逻辑内在耦合,比如,在 UI 中需要绑定处理事件、在某些时刻状态发生变化时需要通知到 UI,以及需要在 UI 中展示准备好的数据。

React 并没有采用将标记与逻辑进行分离到不同文件这种人为地分离方式,而是通过将二者共同存放在称之为“组件”的松散耦合单元之中,来实现关注点分离。

React 不强制要求使用 JSX,但是大多数人发现,在 JavaScript 代码中将 JSXUI 放在一起时,会在视觉上有辅助作用。它还可以使 React 显示更多有用的错误和警告消息。

JSX 的书写规范:

  • JSX 的顶层只能有一个根元素,所以我们很多时候会在外层包裹一个 div 原生;
  • 为了方便阅读,我们通常在 jsx 的外层包裹一个小括号(),这样可以方便阅读,并且 jsx 可以进行换行书写;
  • JSX 中的标签可以是单标签,也可以是双标签;
注意: 如果是单标签,必须以/>结尾;

在 JSX 中嵌入表达式

如果我们 jsx 中的内容是动态的,我们可以通过表达式来获取:

书写规则:{表达式},大括号内可以是变量、字符串、数组、函数调用等任意 js 表达式;

JSX 中的注释

这是嵌入到 JavaScript 中的一种语法,所以在编写注释时,需要通过 JSX 的语法来编写:

<div>
  {/* 我是一段注释 */}
  <h2>Hello World</h2>
</div>

JSX 嵌入变量

情况一: 当变量是 NumberStringArray 类型时,可以直接显示

情况二: 当变量是 nullundefinedBoolean 类型时,内容为空;

  • 如果想要显示 nullundefinedBoolean,那么需要转成字符串;转换的方式有很多,比如 toString 方法、和空字符串拼接,String(变量)等方式;

情况三: 对象类型不能作为子元素(not valid as a React child

class App extends React.Component {
  render() {
    let data = {
      name: 'xhs-rookies',
      age: 18,
      skills: ['JavaScript', 'React', 'Webpack'],

      test1: null,
      test2: undefined,
      flag: false,

      friend: {
        name: 'xhs-zymxxxs',
        age: 29,
      },
    }
    return (
      <div>
        <div>
          {/* 我是一段注释 */}
          <h2>Hello React</h2>
        </div>

        <div>
          {/* 1.可以直接显示 */}
          <h2>{data.name}</h2>
          <h2>{data.age}</h2>
          <h2>{data.skills}</h2>

          {/* 2.不显示 */}
          <h2>{data.test1}</h2>
          <h2>{data.test1 + ''}</h2>
          <h2>{data.test2}</h2>
          <h2>{data.test2 + ''}</h2>
          <h2>{data.flag}</h2>
          <h2>{data.flag + ''}</h2>

          {/* 3.不显示 */}
          <h2>123{data.friend}</h2>
        </div>
      </div>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('app'))

为什么 null、undefined、Boolean 在 JSX 中要显示为空内容呢? 原因是在开发中会进行很多的判断;

  • 在判断结果为 false 时,不显示一个内容;
  • 在判断结果为 true 时,显示一个内容;

JSX 嵌入表达式

在 JSX 语法中,你可以在大括号内放置任何有效的 JavaScript 表达式。例如,2 + 2,user.firstName 或 formatName(user) 都是有效的 JavaScript 表达式。

在下面的示例中,我们将调用 JavaScript 函数 formatName(user) 的结果,并将结果嵌入到<h1>元素中。

function formatName(user) {
  return user.firstName + ' ' + user.lastName
}

const user = {
  firstName: 'xhs',
  lastName: 'rookies',
}

const element = <h1>Hello, {formatName(user)}!</h1>

ReactDOM.render(element, document.getElementById('root'))

JSX 绑定属性

你可以通过使用引号,来将属性值指定为字符串字面量:

const element = <div className="active"></div>

也可以使用大括号,来在属性值中插入一个 JavaScript 表达式:

const element = <img src={user.avatarUrl}></img>

在属性中嵌入 JavaScript 表达式时,不要在大括号外面加上引号。你应该仅使用引号(对于字符串值)或大括号(对于表达式)中的一个,对于同一属性不能同时使用这两种符号。

事件监听

和原生绑定区别

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

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

我们这里演练一下方式二:

  • btnClick()这样写的原因是 onclick 绑定的后面是跟上 JavaScript 代码;
<button onclick="btnClick()">点我一下</button>
<script>
  function btnClick() {
    console.log('按钮发生了点击')
  }
</script>

React 中是如何操作呢?

我们来实现一下 React 中的事件监听,这里主要有两点不同

  • React 事件的命名采用小驼峰式(camelCase),而不是纯小写;
  • 我们需要通过{}传入一个事件处理函数,这个函数会在事件发生时被执行;
class App extends React.Component {
  render() {
    return (
      <div>
        <button onClick={this.btnClick}>点我一下</button>
      </div>
    )
  }

  btnClick() {
    console.log('React按钮点击了一下')
  }
}

事件的 this 绑定

你必须谨慎对待 JSX 回调函数中的 this,在 JavaScript 中,class 的方法默认不会绑定 this。如果你忘记绑定 this.handleClick 并把它传入了 onClick,当你调用这个函数的时候 this 的值为 undefined

class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this)
  }

  render() {
    // 此语法确保 `handleClick` 内的 `this` 已被绑定。
    return <button onClick={() => this.handleClick()}>Click me</button>
  }
}

如果你想了解 bind 和箭头函数对 this 使用的区别,可以选择查看bind 和箭头函数的区别

事件参数传递

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

情况一:获取 event 对象

  • 很多时候我们需要拿到 event 对象来做一些事情(比如阻止默认行为)
  • 假如我们用不到 this,那么直接传入函数就可以获取到 event 对象;
class App extends React.Component {
  btnClick(e) {
    e.preventDefault()
    console.log(e)
  }
  render() {
    return (
      <div>
        <a href="https://xhs-rookies.com/" onClick={this.btnClick}>
          点我一下
        </a>
      </div>
    )
  }
}

情况二:获取更多参数

  • 有更多参数时,我们最好的方式就是传入一个箭头函数,主动执行的事件函数,并且传入相关的其他参数;
class App extends React.Component {
  render() {
    let data = {
      names: ['衣服', '鞋子', '裤子'],
    }
    return (
      <div>
        <a href="https://xhs-rookies.com/" onClick={this.aClick}>
          点我一下
        </a>

        {data.names.map((item, index) => {
          return (
            <a href="#" onClick={(e) => this.aClick(e, item, index)}>
              这里是{item}
            </a>
          )
        })}
      </div>
    )
  }

  aClick(e, item, index) {
    e.preventDefault()
    console.log(item, index)
    console.log(e)
  }
}

条件渲染

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

React 中,所有的条件判断都和普通的 JavaScript 代码一致;

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

条件判断语句

一种方式是当逻辑较多时,通过条件判断:

class App extends React.Component {
  render() {
    let data = {
      isLogin: true,
    }
    let titleJsx = null
    if (data.isLogin) {
      titleJsx = <h2>欢迎回来~</h2>
    } else {
      titleJsx = <h2>请先登录~</h2>
    }

    return <div>{titleJsx}</div>
  }
}

当然,我们也可以将其封装到一个独立的函数中:

class App extends React.Component {
  this.data = {
    isLogin: true
  }

  render() {
    return (
      <div>
        {this.getTitleJsx()}
      </div>
    )
  }

  getTitleJsx() {
    let titleJsx = null;
    if (this.data.isLogin) {
      titleJsx = <h2>欢迎回来~</h2>
    } else {
      titleJsx = <h2>请先登录~</h2>
    }
    return titleJsx;
  }
}

三元运算符

另外一种实现条件渲染的方法就是三元运算符:condition ? true : false;

三元运算符适用于没有太多逻辑的代码:只是根据不同的条件直接返回不同的结果

class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      isLogin: true,
    }
  }

  render() {
    return (
      <div>
        <h2>{this.state.isLogin ? '欢迎回来~' : '请先登录~'}</h2>
        <button onClick={(e) => this.loginBtnClick()}>
          {this.state.isLogin ? '退出' : '登录'}
        </button>
      </div>
    )
  }

  loginBtnClick() {
    this.setState({
      isLogin: !this.state.isLogin,
    })
  }
}

与运算符&&

在某些情况下,我们会遇到这样的场景:

  • 如果条件成立,渲染某一个组件;
  • 如果条件不成立,什么内容也不渲染;

如果我们使用三元运算符,是如何做呢?

{
  this.state.isLogin ? <h2>{this.state.username}</h2> : null
}

其实我们可以通过逻辑与&&来简化操作:

{
  this.state.isLogin && <h2>{this.state.username}</h2>
}

列表渲染

列表渲染

在开发中我们会从服务器请求到大量的数据,数据会以数组的形式存储。

我们需要通过 JavaScript 代码的方式组织数据,转成 JSX

我们来演练一个案例:

class App extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      skills: ['HTML', 'CSS', 'JavaScript', 'React', 'Node'],
    }
  }

  render() {
    return (
      <div>
        <h2>前端技能</h2>
        <ul>
          {this.state.skills.map((item) => {
            return <li>{item}</li>
          })}
        </ul>
      </div>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('app'))

数组处理

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

  • 比如过滤掉一些内容:filter 函数
  • 比如截取数组中的一部分内容:slice 函数

比如我当前有一个数组中存放了一系列的数字:[10, 30, 120, 453, 55, 78, 111, 222]

案例需求:从给定数组中获取所有大于等于 50 的数字,并且展示前 3 个数字

class App extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      numbers: [10, 30, 120, 453, 55, 78, 111, 222],
    }
  }

  render() {
    return (
      <div>
        <h2>数字列表</h2>
        <ul>
          {this.state.numbers
            .filter((item) => item >= 50)
            .slice(0, 3)
            .map((item) => {
              return <li>{item}</li>
            })}
        </ul>
      </div>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('app'))

列表的 key

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

image.png列表展示警告

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

至于为什么需要key,这里涉及到react组件渲染的规则,在特别长列表的时候,或是说DOM节点的子元素发送改变,像是在子元素列表尾部新增元素的时候,只是需要将新增的内容放置于尾部即可,这没有什么问题。

但是如果需要在列表头部新增元素,那么开销会非常大。为了解决这类似的问题,React引入了,使用 key 进行优化后,树的转换效率会提升。

详细关于 key 的内容请见:

列表 & Key – React (reactjs.org)

深入理解为什么 key 是必须的

JSX 原理解析

JSX 转换本质

实际上,jsx 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖。所有的 jsx 最终都会被转换成React.createElement的函数调用。如果你想深入了解 JSX 的实现原理,请详见深入 JSX

案例练习

列表展示

真实开发中,我们的数据通常会从服务器获取,比较常见的是获取一个列表数据,保存到一个数组中进行展示

  • 比如现在有一个代办事项列表,我们如何通过 React 进行展示呢?

我们还是通过一个组件来完成:

class App extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      things: ['写文章', '开会', '上课', '读书'],
    }
  }

  render() {
    return (
      <div>
        <h2>代办事项列表</h2>
        <ul>
          {this.state.things.map((item, index) => {
            return <li>{index + 1 + '.' + item}</li>
          })}
        </ul>
      </div>
    )
  }
}

点赞功能案例

代办事项列表的案例中并没有交互,我们再来实现一个点赞功能的案例:

class App extends React.PureComponent {
  constructor() {
    super()
    this.state = {
      isLike: false,
      likeNum: 233,
    }
  }

  likeOrUnlike() {
    if (this.state.isLike) {
      this.setState({
        isLike: false,
        likeNum: this.state.likeNum - 1,
      })
    } else {
      this.setState({
        isLike: true,
        likeNum: this.state.likeNum + 1,
      })
    }
  }

  render() {
    console.log('render')
    return (
      <div>
        <p>点赞人数:{this.state.likeNum}</p>
        <button onClick={(e) => this.likeOrUnlike()}>
          {this.state.isLike ? '取消点赞' : '点赞'}
        </button>
      </div>
    )
  }
}

小和山的菜鸟们
377 声望2.1k 粉丝

每日进步的菜鸟,分享前端学习手册,和有心学习前端技术的小伙伴们互相探讨,一同成长。