理解JSX

React文档课-开始使用React,本节介绍如何在项目中引入React

Note

本站点包含很多可实时运行的Demo,在PC端阅读将获得更好的体验!

相信大家接触 React 最开始,肯定会对 JSX 感到非常好奇。不得不说在 React 推广初期,JSX 也是其能够吸引很多用户的原因。JSX 其实不是什么神奇的东西,他只是简化了一个特定的函数的调用方式,他的目的无非就是:让代码更易于书写和阅读。所以我们学习 JSX 不要神化他,同时也要知晓其到底是什么东西。

JSX 的语法定义

JSX 是 React 官方推荐在项目中使用的语法,React 对于 JSX 定义其实非常的简单:

JSX 只是对于React.createElement(component, props, ...children) 的语法糖

JSX 看起来非常类似于 HTML,这是特意而为之的,因为 HTML 节点是 Web 页面的基本元素,虽然我们是通过 JS 来实现我们的应用,但实际上我们写的 JS 最终目的仍然是操作 HTML 节点。如果你使用过 jQuery,YUI 这类的工具类库,你会发现你其实经常要直接操作某个/某些 DOM 节点;如果你写过 angular/backbone 这类类库,你会发现你需要写一些类 HTML 模板;所以我们进行 Web 开发是无法避免和 HTML 打交道的,在 React 中同样如此。

而 React 因为引入了虚拟 DOM,我们虽然最终仍然要更新 DOM,但是最终这一步并不需要我们来做,React 会帮助我们完成。取而代之的是我们要创建一系列虚拟 DOM 节点,或者称之为:React 节点(ReactElement)。这类节点不再是直接的 HTML 节点,而是 React 定义的 JS 对象:

type ReactElement = {
  type: T;
  props: P;
  key: Key | null;
};

我们需要通过React.createElement函数来创建这类节点,而这个函数如何调用?自然就是上面提到的React.createElement(component, props, ...children)

<Component id="component">
  <span>Hello</span>
</Component>
1
component

这是第一个参数,代表着组件的类型。

2
props.id

这些类似 HTML attribute 的写法,会被转换成 key value 形式的对象,这些属性被称为props

3
children

写在组件标签中间的,会被编译成...children,也就代表着他们是Component的孩子节点。因为可以有多个孩子节点,每个节点都会直接作为createElement的后续参数传入,所以是...children

学习 JSX 的重点就是,不要把他们看作是 HTML 节点,而是 JS 函数调用,这回让你对于 JSX 的理解上升一整个档次。在你习惯这样思考之后,你再看到 JSX 代码你脑子里面都会直接反射出他的 JS 代码,这时候你就已经如火纯青了。

组件类型

React 的组件类型,其实也就是React.createElement函数的第一个参数,是的从这一刻开始我已经开始锻炼你以 JS 函数的方式来思考 JSX 代码的能力了。那么这个参数可以是什么类型呢?整体上其实就分为两种:

  • 字符串
  • 自定义
  • 内置组件

字符串

字符串指代的是原生的 HTML 节点,就跟你写 HTML 一样,div/span/p等。如果传给React.createElement第一个参数是字符串,React 会知道后续要按照 DOM 节点的方式去处理,并且最终会渲染到页面上。而对于自定义类型,则是按照我们普遍认知的组件的方式进行处理。

原生 Dom 节点必须要用小些字母开头,JSX 编译器根据首字母是小写还是大写来进行编译:

<div>Hello</div>
<Div>Hello</Div>

对于上面两种情况,JSX 的编译器会产出不同的结果:

React.createElement('div', {}, 'Hello');
React.createElement(Div, {}, 'Hello');

对于第二个情况,我们知道Div肯定是一个变量,你需要在作用域中有定义这个变量,不然这个代码机会报错。所以首字母的大小写是 JSX 编译结果的一个非常重要的指标,请务必牢记。

Note

对于自定义组件,你的组件变量必须要在作用域中存在,比如:

const element = <Div>Hello</Div>;

如果你的文件中只有这一行代码,那么他大概率是要报错的,因为找不到Div这个变量,就像你写const a = b + 1但是你并没有定义b的时候一样。

自定义

而自定义又可以分为:

  • 函数组件
  • 类组件

从能力上讲,他们没有本质的区别,他们都是用来渲染节点片段的。但是他们的定义方式上有很多的区别,最大的区别就在与函数组件可以使用 hooks,而类组件则多了生命周期的一些概念。自从 hooks 出来之后,函数组件的重要性得到了无限的提高,从官方的角度上来讲 hooks 就是未来,所以类组件其实已经淡出历史舞台了,我们也会以函数组件为核心来进行后续的学习,React 的新官网的内容也是优先以 hooks 为主的。

内置组件

内置组件也就是 React 内置的一些组件,虽然他们也可以作为组件传递给React.createElement,但是他们和我们认知的组件还是有很大差距的。本质上来说他们并不渲染什么,他们更多是一个标志,React 内置的组件有如下:

  • Fragment
  • StrictMode
  • Suspense
  • Profiler
  • Lazy(React.lazy的返回值)

以上这些组件都只是一个简单的Symbol,他们并没有任何的实现,也没有 render 函数,就只是一个简单的标志符。在 React 的定义中他们被称为ExoticComponent

为什么一个Symbol能称为一个组件呢?要理解这个需要你对 React 的渲染方式进行理解,在这里不多说,因为太过复杂,未来在讲解 React 的高级内容时会进行讲解。现在你只需要理解一点,组件本身对于 React 来说只是一个标志符,他可以根据组件类型来判断是否需要执行组件的render函数,对于内置组件,React 并不需要执行什么,只需要知道他们的含义,以及如何处理他们的 props 即可。

表达式

在 JSX 中可以非常方便地使用 JS 表达式,表达式让 JSX 变得非常灵活,几乎所有你写 JS 能实现的功能都可以在 JSX 中实现。那么为什么 JSX 中可以如此方便地使用表达式呢?还是因为 JSX 仅仅只是函数调用的语法糖,他并没有改变或者扩展 JS 的语法。

React.createElement(Component, {key: '123'}, 'Children');

对于这样一段 JS 代码,你自然是可以非常方便的在其中使用表达式的,比如你的key可以是动态生成的,{key: i+1}

在 JSX 中要使用表达式,需要使用{}进行标识,在{}中的代码,编译器就知道他是一个表达式。上面的例子翻译成 JSX 如下:

<Component key={i + 1}>Children</Component>

直接在children使用字面量会编译成字符串,上面例子中的Children写成{'Children'}是一样的效果。

Children

官方文档已经非常详细。

children也是 props 的一部分,这一点大家也是知道,但是我们上面讲到React.createElement函数的时候我们可以看到,children和 props 并不是一起传递的,那么为什么children又是 props 的一部分呢?

要理解这一点其实也很简单,就是看React.createElement的返回:

interface ReactElement<
  P = any,
  T extends string | JSXElementConstructor<any> =
    | string
    | JSXElementConstructor<any>
> {
  type: T;
  props: P;
  key: Key | null;
}

可以看到这里面并没有children,这是因为在执行函数的过程中,React 把children合并到了 props 中。

专辑

在视屏中我会陪你一起阅读这部分 React 的源码,让你知道React.createElement到底做了什么神奇的事情。

查看视频课程

小结

理解 JSX 最重要就是知道他编译到 JS 之后是什么样的,只要你能快速切换 JSX 和 JS 的代码,那么未来你遇到 JSX 的问题就能非常快速地解决。