在 React 中 JSX 语法不是必须的,它是一种语法糖,最终被转换成 React.createElement() 函数调用,为了提高程序的可读性,推荐使用 JSX 语法而非 React.createElement 函数。
JSX片段通常出现在组件的render方法中,写法如下:
<div>
<div>这是JSX语法</div>
<div>如果要访问变量,那么要将变量名用花括号括起来, {name}</div>
</div>
JSX 片段与 HTML 片段很像,实际上它们存在差异,JSX 片段可以包含 JavaScript 表达式,只需要将其用花括号括起来,另外 JSX 标签上的部分属性名与 HTML 标签上的存在差异,比如:HTML 标签的class 属性在JSX中变成了 className,总体而言,JSX 的属性名变成了 camelCase 的形式而非lowercase 的形式。
JSX 片段除了能直接写在组件的 render 方法中,还能写在单独的模块文件中,等需要使用的时候,再从模块文件导入,示例代码如下:
import { Component } from 'react'
// 导入JSX片段
import { jsx } from './jsxModule'
export default class JSXSyntax extends Component {
render() {
return jsx
}
}
从上述代码可以看出,JSX 片段只是一个普通的变量。由于 ES modules 支持绝对路径作为模块说明符,所以能从其他地方,例如:CDN 导入 JSX 片段。
React.createElement函数
JSX 语法最终会转换成 React.createElement 函数, 其调用形式为React.createElement(type, props, ...children),最终返回一个类型为 React.ReactElement 的简单 JavaScript 对象,type 的数据类型受JSX标签影响。下面的示例代码演示 JSX 语法与 React.createElement 之间的转换。
const HelloJSX1 = <div>hello jsx</div>
const HelloJSX2 = React.createElement('div', null, 'hello jsx')
上述 HelloJSX1 与 HelloJSX2 的效果一样,HelloJSX1 的写法最终被转成 HelloJSX2 的形式。Babel 有一个在线的编译器 能将 JSX 片段转成 React.createElement,通过它能快速查看 JSX 的转换结果,值得一试。
JSX 的标签
JSX标签有多种写法,比如以小写字母开头、以大写字母开头,对象点语法等。JSX标签决定了React element的类型,JSX片段和React.createElement函数最终生成的就是React element。
-
以小写字母开头的标签
如果 JSX 标签以小写字母开头,那么字符串会被传给 React.createElement 的第一个参数,这时React 会认为正在使用浏览器内置的组件,如 div,p 等。示例代码如下:
// 'div'做为参数
<div>hello jsx</div> => React.createElement('div', null, 'hello jsx') // line A
// 'error'做为参数
<error>会出错</error> => React.createElement('error', null, '会出错') // line B
上述代码 line B 会报错,是因为浏览器没有内置的元素 error。
-
以大写字母开头的标签
如果 JSX 标签以大写字母开头,那么标签名对应的变量会传给 React.createElement 的第一个参数,这时 React 会认为正在使用用户自定义的组件。示例代码如下:
<Error>自定义组件</Error> => React.createElement(Error, null, '自定义组件')
上述代码将 Error 传给 React.createElement 函数,而非将 'Error' 传给它,要想函数成功运行,必须使自定义组件 Error 能在作用域内访问到。
-
以对象点语法作为标签
对象点语法可以出现在标签中,但中括号语法不能,这是因为点语法只是静态的变量访问,中括号语法是动态的表达式。示例代码如下:
const MyObj = {
name: () => <div>bella</div>
}
const nameDiv = <MyObj.name/> // 点语法没有问题
const nameDivError = <MyObj['name']/> // 报错!不能用中括号语法
const Name = MyObj['name']
const nameOk = <Name/> // 将变量作为标签,没问题
不能将中括号语法直接用到标签上,但可以将它赋给一个变量,再将变量作为标签。不管 JSX 标签是那种写法,必须让 React 在作用域中
JSX 的属性
任何合法的 JavaScript 数据类型和表达式都能作为 JSX 属性的值,但要用花括号将值括起来,字符串字面量能省略花括号直接赋值,ES6 中的扩展运算符可以出现在属性中。本小节不介绍如何给属性赋值,而是将目光放在属性与 TypeScript 类型检查上,如果要学习如何给属性赋值,推荐到 React 官方网站查看详细内容。
在 React 程序中,除了可以使用自定义的组件还能使用浏览器内置的组件,如:div,p等。内置的组件能设置哪些属性,在 React 类型声明文件中已经定义好了;自定义的组件能设置哪些属性由开发人员自己决定。
-
内置组件的属性
提前定义好内置组件能设置的属性,极大地简化了开发人员的工作,使开发人员得益于代码提示。但在某些时候会有麻烦,比如搭建 React 开发环境一文中介绍使用 babel-plugin-react-css-modules 获得设置 CSS 类名的便利,它在编译阶段会将 styleName 转换成 className,但 TypeScript 编译器不知道这一点,由于 React 类型声明文件没有给内置组件定义名为 styleName 的属性,所以如果给内置组件设置 styleName,此时 TypeScript 编译器会报错,错误提示大概是:类型“XXX”上不存在属性“styleName”。
<div styleName='app'/> // TypeScript编译器会报错
扩展 react 模块的类型能解决这个麻烦,见下面的代码清单 1:
declare module 'react' {
interface Attributes {
styleName?: string;
}
}
// 告诉TypeScript编译器这是模块的声明文件,而不是全局变量的声明文件
export {}
在 src 目录下创建一个以 .d.ts 为扩展名的文件,然后将上述代码放在该文件中,就能解决给内置组件设置 styleName 属性 TypeScript 编译器报错的问题。通过这个方式除了能扩展 react 模块的类型还能扩展其他第三方模块的类型。
-
自定义组件的属性
自定义的组件能设置哪些属性,这由开发人员自己决定。自定义组件分为函数组件和类组件,它们的写法不同,但定义 props 的类型是类似的,通常是创建一个接口,在这个接口中指定组件能设置的属性,代码如下所示:
// 在这个接口中指定组件能设置的属性有哪些
interface Props {
name: string;
}
// 函数组件
function FunctionComponent(props: Props ) {
// 此处写代码
}
// 类组件
class ClassComponent extends React.Component<Props> {
// 此处写代码
}
<ClassComponent name='Bella'/> // 不报错
<ClassComponent /> // 缺少name,编译器报错
<ClassComponent name='Bella' age={12}/> // 多了age,编译器报错
从上述代码可以看出,使用自定义组件时,不管给它设置的属性太多还是太少,TypeScript 编译器都会报错,所以在创建自定义组件时要明确的知道它们能接收哪些属性。如果所有的自定义组件都要接收同一个属性,给这些组件的 Props 接口中都设置这个属性难免显得麻烦。此时,代码清单 1 用到的方式能解决这个麻烦事。
如果你对 React 已经有一定的了解,可曾思考过为什么所有的组件都能传一个名为key的属性呢?答案是在 React 的类型文件中给 Attributes 接口设置了一个默认的属性 key,代码如下:
interface Attributes {
key?: Key | null | undefined;
}
代码清单 1 扩展的就是这个 Attributes 接口。查看 React 类型声明文件能发现,Attributes 接口与开发人员自定义的 Props 接口生成联合类型,该联合类型用于注释组件 props 的类型,而非直接用开发人员自定义的 Props 接口注释组件 props 的类型。
小节
JSX 不是 React 特有的,但它是学习 React 框架时,需要掌握的一个重要的知识点,想在 TS 项目中用好 React 建议多查看 React 的类型定义文件。
发表评论