# 深入 JSX
根本上讲,JSX 只是提供了对 React.createElement(component, props, ...children) 函数的语法糖。JSX 代码:
~~~
<MyButton color="blue" shadowSize={2}>
Click Me
</MyButton>
~~~
被编译为:
~~~
React.createElement(
MyButton,
{color: 'blue', shadowSize: 2},
'Click Me'
)
~~~
你也可以使用自封闭形式的标签,如果它没有 children。如:
~~~
<div className="sidebar" />
~~~
被编译为:
~~~
React.createElement(
'div',
{className: 'sidebar'},
null
)
~~~
如果你希望测试某些特别的 JSX 如何被转换成 JavaScript,可以尝试使用[在线 Babel 编译器](https://babeljs.io/repl/#?babili=false&evaluate=true&lineWrap=false&presets=es2015%2Creact%2Cstage-0&code=function%20hello\(\)%20%7B%0A%20%20return%20%3Cdiv%3EHello%20world!%3C%2Fdiv%3E%3B%0A%7D)。
## 指定反射的元素类型
JSX 标签的第一部分确定了 反射的元素的类型。
大写的类型表示 JSX 标签是提及一个 React 组件。这些标签被编译为一个直接的对命名变量的引用,所以如果你使用 JSX `<Foo />` 表达式,Foo 必须在作用域内。
### React 必须在作用域内
由于 JSX 编译器为 React.createElement 的调用,React 库必须总是在你的 JSX 代码的作用域中。
例如,所有的 imports 在这段代码中都是必须的,即使 React 和 CustomButton 没有直接从 JavaScript 中引用:
~~~
import React from 'react';
import CustomButton from './CustomButton';
function WarningButton() {
// return React.createElement(CustomButton, {color: 'red'}, null);
return <CustomButton color="red" />;
}
~~~
如果你不使用一个 JavaScript 包 而是添加 React 作为一个 script 标签,它已经作为一个全局 React 存在。
### 对于 JSX 类型使用点语法
在 JSX 中也可以使用 点语法引用一个 React 组件。如果你有一个单独的模块 exports 了许多 React 组件,这将会很方便。例如,如果 MyComponent.DatePicker 是一个 组件,你可以直接在 JSX 中使用它:
~~~
import React from 'react';
const MyComponents = {
DatePicker: function DatePicker(props) {
return <div>Imagine a {props.color} datepicker here.</div>;
}
}
function BlueDatePicker() {
return <MyComponents.DatePicker color="blue" />;
}
~~~
### 用户定义组件必须是大写
当一个元素类型以小写字母开始,它引用一个内置的组件如 `<div>` 或者 `<span>`,并使一个字符串 'div' 或者 'span' 传递到 React.createElement 中。而由大写字母开头的类型如 `<Foo />` 编译为 React.createElement(Foo) 并对应定义的组件或者 JavaScript 文件中导入的组件。
我们建议使用大写字母命名组件。如果你的确有一个以小写字母开头的组件,可以在 JSX 中使用它之前,分配它到一个大写字母开头的变量。
例如,这段代码将不能正常运行:
~~~
import React from 'react';
// Wrong! This is a component and should have been capitalized:
function hello(props) {
// Correct! This use of <div> is legitimate because div is a valid HTML tag:
return <div>Hello {props.toWhat}</div>;
}
function HelloWorld() {
// Wrong! React thinks <hello /> is an HTML tag because it's not capitalized:
return <hello toWhat="World" />;
}
~~~
要修复它,我们可以重命名 hello 为 Hello 并引用时使用 `<Hello />`:
~~~
import React from 'react';
// Correct! This is a component and should be capitalized:
function Hello(props) {
// Correct! This use of <div> is legitimate because div is a valid HTML tag:
return <div>Hello {props.toWhat}</div>;
}
function HelloWorld() {
// Correct! React knows <Hello /> is a component because it's capitalized.
return <Hello toWhat="World" />;
}
~~~
### 在运行时选择类型
不能使用一个一般的表达式作为 React 元素类型。如果你真的想要使用一个一般表达式来表示元素类型,只要先分配它到一个大写变量。这通常在你想要基于一个 prop 来渲染一个不同的组件时使用:
~~~
import React from 'react';
import { PhotoStory, VideoStory } from './stories';
const components = {
photo: PhotoStory,
video: VideoStory
};
function Story(props) {
// Wrong! JSX type can't be an expression.
return <components[props.storyType] story={props.story} />;
}
~~~
要修复它,我们将分配类型到一个大写字母的变量:
~~~
import React from 'react';
import { PhotoStory, VideoStory } from './stories';
const components = {
photo: PhotoStory,
video: VideoStory
};
function Story(props) {
// Correct! JSX type can be a capitalized variable.
const SpecificStory = components[props.storyType];
return <SpecificStory story={props.story} />;
}
~~~
## JSX 中的 props
有几种不同的方式在 JSX 中指定 props 。
### JavaScript 表达式
你可以传递任何 JavaScript 表达式作为一个 props,使用花括号包围它。例如,在这个 JSX 中:
~~~
<MyComponent foo={1 + 2 + 3 + 4} />
~~~
对于 MyComponent,props.foo 的值将是 10,因为 表达式 1+ 2+3+4 被计算结果。
if 语句和 for 循环不是 JavaScript 格式的表达式,那么它们不能直接用在 JSX 中。反之,你可以把它们放进 花括号中。例如:
~~~
function NumberDescriber(props) {
let description;
if (props.number % 2 == 0) {
description = <strong>even</strong>;
} else {
description = <i>odd</i>;
}
return <div>{props.number} is an {description} number</div>;
}
~~~
### 字符串字面量
可以传递字符串字面量作为 一个 prop。这两个 JSX 表达式是等效的:
~~~
<MyComponent message="hello world" />
<MyComponent message={'hello world'} />
~~~
当你传递了一个字符串字面量,它的值是 HTML 转义的。所以这两个 JSX 表达式是等效的:
~~~
<MyComponent message="<3" />
<MyComponent message={'<3'} />
~~~
这个行为通常是没有关系的。这里只有涉及到完整性。
### Props 默认为 "True"
如果你没有传递值到 prop,它默认为 true。这两个 JSX 表达式是等效的:
~~~
<MyTextBox autocomplete />
<MyTextBox autocomplete={true} />
~~~
普遍来说,我们不建议使用这个,因为它可能和 ES6 的对象 {foo: foo} 而不是 {foo: true} 的短语法 {foo} 混淆。这个行为只在这里所以匹配 HTML 的行为。
### 扩展的属性
如果你已经有一个对象的属性,你希望在 JSX 中传递它,可以使用 `...` 作为一个 扩展的操作符来传递整个 props 对象。这两个组件是等效的:
~~~
function App1() {
return <Greeting firstName="Ben" lastName="Hector" />;
}
function App2() {
const props = {firstName: 'Ben', lastName: 'Hector'};
return <Greeting {...props} />;
}
~~~
扩展属性在你构建一般容器的时候是有用的。然而,它们可能使你的代码混乱,因为容易传递大量不相干的 props 到组件而并不关心它们的内容。我们建议你保守的使用这个语法。
## JSX 中的 Children
In JSX expressions that contain both an opening tag and a closing tag, the content between those tags is passed as a special prop: props.children. There are several different ways to pass children:、
在包含一个开口标签和一个闭口标签的 JSX 表达式中,在它的标签之间的内容被传递为一个特别的 prop:props.children 。还有几种不同的方式来传递 children:
### 字符串字面量
你可以在标签之间放置字符串,props.children 则是这个字符串。对于多个内置的 HTML 元素 这是有用的。例如:
~~~
<MyComponent>Hello world!</MyComponent>
~~~
这是有效的 JSX ,MyComponent 中的 props.children只是简单的字符串”Hello world!“。HTML 是被转义的,所以你可以一般编写 JSX 就像编写 HTML 一样的方式:
~~~
<div>This is valid HTML & JSX at the same time.</div>
~~~
JSX 移除了开头和结尾处的空白。它也移除了空行。邻近标签的新行被移除;出现在字符串字面量中的新行被认为是一个单独的空格。所以它们渲染相同内容:
~~~
<div>Hello World</div>
<div>
Hello World
</div>
<div>
Hello
World
</div>
<div>
Hello World
</div>
~~~
### JSX Children
你可以提供更多 JSX 元素作为 children。这用于显示嵌套的组件:
~~~
<MyContainer>
<MyFirstComponent />
<MySecondComponent />
</MyContainer>
~~~
你可以混合不同的类型的 children 在一起使用,所以你可以使用字符串字面量和 JSX children。这是JSX 像 HTML 的另外一种方式,所以都是有效的 JSX 和 有效的 HTML:
~~~
<div>
Here is a list:
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
</div>
~~~
一个 React 组件不能返回多个 React 元素,但是一个单独的 JSX 表达式可以有多个 children,所以如果你想要一个组件渲染多个内容,可以包装它们到一个 div 中,就像这样。
### JavaScript 表达式
You can pass any JavaScript expression as children, by enclosing it within {}. For example, these expressions are equivalent:
可以传递任何 JavaScript 表达式作为 children,通过封闭的 花括号包括。例如,这些表达式是等效的:
~~~
<MyComponent>foo</MyComponent>
<MyComponent>{'foo'}</MyComponent>
~~~
通常用于渲染一个任意长度 JSX 表达式的列表。例如,这渲染了一个 HTML 列表:
~~~
function Item(props) {
return <li>{props.message}</li>;
}
function TodoList() {
const todos = ['finish doc', 'submit pr', 'nag dan to review'];
return (
<ul>
{todos.map((message) => <Item key={message} message={message} />)}
</ul>
);
}
~~~
JavaScript 表达式可以混合其它类型的 children 使用。通常用于字符串模板得地方:
~~~
function Hello(props) {
return <div>Hello {props.addressee}!</div>;
}
~~~
### 函数作为 children
通常,JavaScript 表达式插入到 JSX 中将执行为一个字符串,一个 React 元素,或者一个这些内容的列表。
然而,props.children 就像其它 prop 那样可以传人任何类型的数据,不只是 React 知道如何渲染的类型。例如,如果你有一个自定义组件,可以让它有一个回调作为 props.children:
~~~
function ListOfTenThings() {
return (
<Repeat numTimes={10}>
{(index) => <div key={index}>This is item {index} in the list</div>}
</Repeat>
);
}
// Calls the children callback numTimes to produce a repeated component
function Repeat(props) {
let items = [];
for (let i = 0; i < props.numTimes; i++) {
items.push(props.children(i));
}
return <div>{items}</div>;
}
~~~
传递到一个自定义组件的 children 可以是任何内容,只要这个组件能转换它们到一些 React 在渲染前能理解的东西。这个用法并不常见,但是它在如果你想要扩展 JSX 的能力时可以使用。
### Booleans,Null,和 Undefined 被忽略
false,null,undefined和 true 是有效的 children。它们只是简单的不会渲染。这些 JSX 表达式都会渲染相同的内容:
~~~
<div />
<div></div>
<div>{false}</div>
<div>{null}</div>
<div>{true}</div>
~~~
这可以用于条件渲染 React 元素。这个 JSX 只在 showHeader 为 true 时渲染一个 `<Header />`:
~~~
<div>
{showHeader && <Header />}
<Content />
</div>
~~~
一个警告是,一些 [”falsy“的值](https://developer.mozilla.org/en-US/docs/Glossary/Falsy),比如数字 0,仍然会被 React 渲染。例如,这段代码不会按照你预期的发生,因为在 props.messages 是一个空数组时 0 会被打印:
~~~
<div>
{props.messages.length &&
<MessageList messages={props.messages} />
}
</div>
~~~
要修复这个问题,确保 && 之前的表达式总是布尔值:
~~~
<div>
{props.messages.length > 0 &&
<MessageList messages={props.messages} />
}
</div>
~~~
相反,如果你想要一个值比如false,true,null 或者 undefined 显示在输出中,你需要[转换它们为字符串](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#String_conversion):
~~~
<div>
My JavaScript variable is {String(myVariable)}.
</div>
~~~