JSX In Depth

本质上,JSX只是React.createElement(component, props, ...children)的语法糖,比如下面代码:

  1. <MyButton color="blue" shadowSize={2}>
  2. Click Me
  3. </MyButton>

将被编译成:

  1. React.createElement(
  2. MyButton,
  3. {color: 'blue', shadowSize: 2},
  4. 'Click Me'
  5. )

如果没有子标签的话,也可以使用自闭和标签:

  1. <div className="sidebar" />

会被编译成:

  1. React.createElement(
  2. 'div',
  3. {className: 'sidebar'},
  4. null
  5. )

如果想测试JSX如何被编译成JavaScript,可以尝试Babel在线编译器


指定React的元素类型

JSX标签的第一部分明确了React元素的类型。

使用大写的JSX标签来表示React的一个组件。这些标签编译成对命名变量的直接的引用,所以,如果使用JSX标签<Foo />表达式,则Foo必须在作用域内。(译者注:必须引入组件)

作用域内必须引入React

由于JSX会被编译成React.createElement,因此在JSX的代码中,必须引入React库。

例如,下面代码中即使ReactCustomButton没有直接在JavaScript中使用,但是这两者都必须在一起引入:

  1. import React from 'react';
  2. import CustomButton from './CustomButton';
  3. function WarningButton() {
  4. // return React.createElement(CustomButton, {color: 'red'}, null);
  5. return <CustomButton color="red" />;
  6. }

如果使用<script>标签加载React,而不是使用模块化加载,React也是在作用域内的,此时React是全局作用域中。

JSX使用点(.)操作符

在JSX中可以使用点(.)操作符使用React组件,如果通过一个模块导出多个React组件的话,使用点操作符非常的使用和方便。

例如,如果MyComponents.DatePicker是一个组件,可以在JSC中直接向下面这样使用:

  1. import React from 'react';
  2. const MyComponents = {
  3. DatePicker: function DatePicker(props) {
  4. return <div>Imagine a {props.color} datepicker here.</div>;
  5. }
  6. }
  7. function BlueDatePicker() {
  8. return <MyComponents.DatePicker color="blue" />;
  9. }

自定义组件必须大写

如果定义的组件使用小写开头,他会把<div>,<span>这样的内置组件作为divspan字符串传递给React.createElement

如果以大写开头, 比如<Foo/>会被编译成React.createElement(Foo),并且Foo对应在jsx文件中引入的组件.

React推荐使用大写开头定义组件,如果有小写开头的组件,则在jsx中使用前,应当将其赋值给大写的变量。

例如,下面的代码将不会起作用:

  1. import React from 'react';
  2. // Wrong! This is a component and should have been capitalized:
  3. function hello(props) {
  4. // Correct! This use of <div> is legitimate because div is a valid HTML tag:
  5. return <div>Hello {props.toWhat}</div>;
  6. }
  7. function HelloWorld() {
  8. // Wrong! React thinks <hello /> is an HTML tag because it's not capitalized:
  9. return <hello toWhat="World" />;
  10. }

为了解决上面的问题,需要将hello改成Hello,并且使用<Hello/>:

  1. import React from 'react';
  2. // Correct! This is a component and should be capitalized:
  3. function Hello(props) {
  4. // Correct! This use of <div> is legitimate because div is a valid HTML tag:
  5. return <div>Hello {props.toWhat}</div>;
  6. }
  7. function HelloWorld() {
  8. // Correct! React knows <Hello /> is a component because it's capitalized.
  9. return <Hello toWhat="World" />;
  10. }

在运行的时候选择组件

不能使用通用表达式来作为React的元素类型,如果你想使用一个通用表达式来指明元素类型,首先需要将其赋值给大写的变量。

这种情况经常发生在需要根据prop来渲染不同的组件的情况:

(下面代码会出错)

  1. import React from 'react';
  2. import { PhotoStory, VideoStory } from './stories';
  3. const components = {
  4. photo: PhotoStory,
  5. video: VideoStory
  6. };
  7. function Story(props) {
  8. // Wrong! JSX type can't be an expression.
  9. return <components[props.storyType] story={props.story} />;
  10. }

为了解决这个问题,需要将标签赋值给大写的变量:

  1. import React from 'react';
  2. import { PhotoStory, VideoStory } from './stories';
  3. const components = {
  4. photo: PhotoStory,
  5. video: VideoStory
  6. };
  7. function Story(props) {
  8. // Correct! JSX type can be a capitalized variable.
  9. const SpecificStory = components[props.storyType];
  10. return <SpecificStory story={props.story} />;
  11. }

JSX中的Props

在JSX中指定props有几种不同的方式。

使用JavaScript表达式作为Props

对于MyComponent,props.foo的值是10,因为1 + 2 + 3 + 4结果是10.

由于if语句和for循环不是JavaScript的表达式,所以不能直接在JSX使用。不过,可以在JSX外部的代码中使用,比如:

  1. function NumberDescriber(props) {
  2. let description;
  3. if (props.number % 2 == 0) {
  4. description = <strong>even</strong>;
  5. } else {
  6. description = <i>odd</i>;
  7. }
  8. return <div>{props.number} is an {description} number</div>;
  9. }

你可以在条件渲染循环部分了解更多。

字符串文本

你可以将字符串文本作为prop,下面两个JSX表达式是等价的:

  1. <MyComponent message="hello world" />
  2. <MyComponent message={'hello world'} />

如果将字符串作为prop,则prop的值是对HTML的转义,因此下面两个JSX表达式是等价的:

  1. <MyComponent message="&lt;3" />
  2. <MyComponent message={'<3'} />

不过两者没直接的什么关系,只是提一下。

Prop的默认值是True

如果没有给prop指定值,则默认值是true,因此下面两个JSX表达式是等价的:

  1. <MyTextBox autocomplete />
  2. <MyTextBox autocomplete={true} />

一般我们不建议这样去使用,因为这将会和ES6对象简写冲突。

比如 {foo} 会被看做{foo:foo}的简写,而不是{foo:true}的简写。

This behavior is just there so that it matches the behavior of HTML.

扩展运算符传递属性

如果你的props作为一个对象,如果在JSX中使用这个对象传递props,可以通过扩展运算符...展开props对象,下面两个组件是等价的:

  1. function App1() {
  2. return <Greeting firstName="Ben" lastName="Hector" />;
  3. }
  4. function App2() {
  5. const props = {firstName: 'Ben', lastName: 'Hector'};
  6. return <Greeting {...props} />;
  7. }

你也可以在利用展开运算符实现一些特殊的props:

  1. const Button = props => {
  2. const { kind, ...other } = props;
  3. const className = kind === "primary" ? "PrimaryButton" : "SecondaryButton";
  4. return <button className={className} {...other} />;
  5. };
  6. const App = () => {
  7. return (
  8. <div>
  9. <Button kind="primary" onClick={() => console.log("clicked!")}>
  10. Hello World!
  11. </Button>
  12. </div>
  13. );
  14. };

在上面的例子中,kind不会传递给DOM中的<button>元素,只会被处理。

而其他的props则会通过...other对象传递给DOM元素,可以看到传递了一个onClickchildren.

虽然扩展运算符能够非常方便的将props传递,但是也会将一些不需要的props或者是无效的HTML属性传递给DOM,所以建议谨慎使用这种语法。


JSX中children props

JSX表达式中,除了自闭和标签外还有非自闭和标签,中间的内容通过一个特殊的prop,props.children。下面有几种不同的方式来传递这些props:

字符串文本

你可以在标签中间放一个字符串,props.children将只是该字符串。这对于许多内置的HTML元素很有用,比如:

  1. <MyComponent>Hello world!</MyComponent>

上述代码在JSX中是有用的,并且MyComponent中的props.children是字符串Hello world!.HTML会进行转移,因此可以像下面这样子写HTML元素:

  1. <div>This is valid HTML &amp; JSX at the same time.</div>

JSX会移除行开始和结尾的空格,同时也会删除空行。

与标签相邻的新行也会被移除,字符串文字中间的新行会被压缩成一个空格。

例如:

  1. <div>Hello World</div>
  2. <div>
  3. Hello World
  4. </div>
  5. <div>
  6. Hello
  7. World
  8. </div>
  9. <div>
  10. Hello World
  11. </div>

JSX子组件

同样,JSX可以将JSX组件作为子元素,这对于显示嵌套的组件很有用:

  1. <MyContainer>
  2. <MyFirstComponent />
  3. <MySecondComponent />
  4. </MyContainer>

可以混合不同类型的子元素,所以可以同时一起使用字符串文本作为JSX的子元素。就像使用HTML一样使用JSX,因此既是有用的JSX也是有用的HTML:

  1. <div>
  2. Here is a list:
  3. <ul>
  4. <li>Item 1</li>
  5. <li>Item 2</li>
  6. </ul>
  7. </div>

React的组件同样能够返回一个html元素数组:

  1. render() {
  2. // No need to wrap list items in an extra element!
  3. return [
  4. // Don't forget the keys :)
  5. <li key="A">First item</li>,
  6. <li key="B">Second item</li>,
  7. <li key="C">Third item</li>,
  8. ];
  9. }

JavaScript表达式作为子元素

你可以将任意的JavaScript表达式作为Children,通过一个闭合的{}即可。例如,下面两个是等价的:

  1. <MyComponent>foo</MyComponent>
  2. <MyComponent>{'foo'}</MyComponent>

经常用于显示任意长度的JSX表达式,比如呈现一个todos列表:

  1. function Item(props) {
  2. return <li>{props.message}</li>;
  3. }
  4. function TodoList() {
  5. const todos = ['finish doc', 'submit pr', 'nag dan to review'];
  6. return (
  7. <ul>
  8. {todos.map((message) => <Item key={message} message={message} />)}
  9. </ul>
  10. );
  11. }

JavaScript 表达式能够和其他类型的表达式混合使用,比如替代字符串模板:

  1. function Hello(props) {
  2. return <div>Hello {props.addressee}!</div>;
  3. }

Children传递方法

通常来说,JSX中的JavaScript表达式会被转为字符串、React元素或者是其他。然而,props.children和其他的prop一样能够传递任何数据,而不仅仅是React能够渲染的内容。例如,如果你有一个自定义组件,你可以将其作为props.children的回调。

  1. // Calls the children callback numTimes to produce a repeated component
  2. function Repeat(props) {
  3. let items = [];
  4. for (let i = 0; i < props.numTimes; i++) {
  5. items.push(props.children(i));
  6. }
  7. return <div>{items}</div>;
  8. }
  9. function ListOfTenThings() {
  10. return (
  11. <Repeat numTimes={10}>
  12. {(index) => <div key={index}>This is item {index} in the list</div>}
  13. </Repeat>
  14. );
  15. }

传递给自定义组件的Children可以是任意的内容,只要组件能够将其转换成能够渲染的内容即可。这种用法并不常见,如果想要扩展JSX的功能,可以这样去使用。

Booleans,Null和Undefined会被忽略

falsenullundefinedtrue都是正确的Children,他们只是不会被渲染。

这些JSX表达式最后渲染的结果是一样的:

  1. <div />
  2. <div></div>
  3. <div>{false}</div>
  4. <div>{null}</div>
  5. <div>{undefined}</div>
  6. <div>{true}</div>

这对于条件渲染React元素很有用,比如下面的例子(如果showHeadertrue,则会渲染<Header/>):

(译者注:这里觉得文档中的表述并不是非常的好,下面的表述某种程度上容易被理解成只会渲染<Header/>而不渲染 <Content/>,虽然这是不可能的)

This JSX only renders a <Header /> if showHeader is true:

  1. <div>
  2. {showHeader && <Header/>}
  3. <Content/>
  4. </div>

不过需要注意的是,一些falsy值(实际上就是JavaScirpt中一些类型转换),比如0,依旧会在React中进行渲染。

例如下面的例子,代码中不会按照预期的渲染,因为props.message是空数组的时候,会被渲染出0,而不会忽略。(译者注:因为0不是标准的Boolean)

  1. <div>
  2. {props.messages.length &&
  3. <MessageList messages={props.messages} />
  4. }
  5. </div>

为了解决上述问题,需要确保在使用&&运算符的时候,前面必须是Boolean类型的:

  1. <div>
  2. {props.messages.length > 0 &&
  3. <MessageList messages={props.messages} />
  4. }
  5. </div>

反之,如果你想将falsetruenull或者undefined渲染出来,你需要将其转成string:

  1. <div>
  2. My JavaScript variable is {String(myVariable)}.
  3. </div>