JSX In Depth

从根本上讲,JSX只是React.createElement(component, props, ...children)函数的语法糖。JSX代码:

  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编译器%20%7B%0A%20%20return%20%3Cdiv%3EHello%20world!%3C%2Fdiv%3E%3B%0A%7D).

指定React元素类型

JSX标签的开始部分决定了React元素的类型。

首字母大写的标签指示JSX标签是一个React组件。这些标签会被编译成命名变量的直接引用。所以如果你使用JSX的<Foo />表达式,Foo必须在作用域中。

React必须在作用域中存在

因为JSX被编译为调用React.createElement的形式,所以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. }

如果你没有选择打包JavaScript,而是通过在script标签中添加了React,那么全局中已经存在React

对JSX类型使用点表示法

在JSX中,你可以通过点表示法引用React组件。如果仅有一个单一模块(module),但却对外提供多个React组件时,点表示法就非常的方便。

  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>的内置组件,会给React.createElement方法传递字符串div或者span。以大写字母开头的类型,类似于<Foo />,将会被编译成React.createElement(Foo),对应于自定义组件或者在JavaScript文件中引入的组件。

我们建议给组件以大写字母开头的方式命名。如果你已经有以小写字母开头的组件,需要在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类型

不能使用普通的表达式作为React元素类型。如果你想使用通用表达式来表示元素类型,首先你需要将其赋值给大写的变量。这通常会出现在出现在根据不同的props渲染不同的组件:

  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传递一个用{}包裹的JavaScript表达式,例如:

  1. <MyComponent foo={1 + 2 + 3 + 4} />

MyComponent对讲,props.foo的值为10,因为表达式1 + 2 + 3 + 4会被计算。

对于JavaScript,if语句和for循环不是表达式,因此不能在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'} />

当给props传递字符串时,其值是未转义的HTML(HTML-unescaped)。下面两种JSX表达式是等价的:

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

这种行为通常讲并不重要,这里所提到的只是为了完整性。

Props Default to “True”

如果prop没有传入值,默认为true。下面两种JSX表达式是等价的:

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

通常情况下,我们不建议使用这种类型,因为这会与ES6中的对象shorthand混淆。ES6 shorthand中{foo}指的是{foo: foo}而不是{foo: true}

属性展开

如果存在一个object类型的props并且想在JSX中传入,你可以使用展开符...传入整个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. }

当你构建一个container时,属性展开非常有用。然而,这可能会使得你的代码非常混乱,因为这使得非常多不相关的props传递给组件,但组件并不需要这些props。因此我们建议谨慎使用该语法。

JSX中的Children

在包括开标签和闭标签在内的JSX表示式中,标签中的内容会被传递一个特殊的props:props.children,下面有好几种方式传递children

字符串

您可以在开标签和闭合标签中写入字符串,这对于内置HTML元素非常有用,例如:

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

这是有效的JSX,MyComponentprops.children属性值为"Hello world!"。HTML是非转义的,因此你可以像写HTML一样来写JSX。

  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 Children

你可以传递多个JSX元素作为子元素,这对显示嵌套组件非常有用:

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

你可以混合不同类型的子元素,因此你可以混用字符串和JSX子元素。这是JSX与HTML另一点相似的地方,因此下面是HTML和JSX均是有效的:

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

React组件不能返回多个React元素,但是单个JSX表达式可以有多个子元素,因此如果你想要渲染多个元素,你可以像上面一样,将其包裹在div

JavaScript 表达式

通过使用{}包裹,你可以将任何的JavaScript元素而作为子元素传递,下面表达式是等价的:

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

这对于渲染长度不定的JSX表达式列表非常有用,例如,下面会渲染HTML list:

  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. }

Function类型的子元素

通常情况下,嵌入JSX中的JavaScript表达式会被认为是字符串、React元素或者是这些内容的列表。然而,props.children类似于其他的props,可以被传入任何数据,而不是仅仅只是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. }

传递给自定义组件的子元素可以是任何类型,只要在渲染之前组件可以将其转化为React能够处理的东西即可。这种用法并不常见,但是如果你需要扩展JSX的话,则会非常有用。

Booleans, Null和Undefined都会被忽略

false, null, undefined, 和 true都是有效的子类型。但是并不会被渲染,下面的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 />会被渲染:

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

需要注意的是,React仍然提供了“falsy”值,例如数值0,仍然会被React渲染。例如:你可能会认为当props.messages为空数组时,将会渲染0,但实际并不是这样:

  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,你必须先将其转化为字符串:

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