# 深入 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>
~~~