JSX高級
本質(zhì)上來講,JSX 只是為 React.createElement(component, props, ...children) 方法提供的語法糖。比如下面的代碼:
<MyButton color="blue" shadowSize=>
Click Me
</MyButton>
編譯為:
React.createElement(
MyButton,
{color: 'blue', shadowSize: 2},
'Click Me'
)
如果沒有子代,你還可以使用自閉合標(biāo)簽,比如:
<div className="sidebar" />
編譯為:
React.createElement(
'div',
{className: 'sidebar'},
null
)
如果你想徹底驗(yàn)證 JSX 是如何轉(zhuǎn)換為 JavaScript 的,你可以嘗試 在線 Babel 編譯器.
指定 React 元素類型
JSX 的標(biāo)簽的第一部分決定了 React 元素的類型。
首字母大寫的類型表示 JSX 標(biāo)簽引用到一個(gè) React 組件。這些標(biāo)簽將會(huì)被編譯為直接引用同名變量,所以如果你使用了 <Foo /> JSX 表達(dá)式,則 Foo 必須在作用域中。
React 必須在作用域中
由于 JSX 編譯成React.createElement方法的調(diào)用,所以在你的 JSX 代碼中,React庫必須也始終在作用域中。
比如,下面兩個(gè)導(dǎo)入都是必須的,盡管 React 和 CustomButton 都沒有在代碼中被直接調(diào)用。
import React from 'react';
import CustomButton from './CustomButton';
function WarningButton() {
// return React.createElement(CustomButton, {color: 'red'}, null);
return <CustomButton color="red" />;
}
如果你沒有使用JavaScript 打捆機(jī),而是從<script>標(biāo)簽加載React,它已經(jīng)在作用域中,以React全局變量的形式。
點(diǎn)表示法用于JSX類型
你還可以使用 JSX 中的點(diǎn)表示法來引用 React 組件。你可以方便地從一個(gè)模塊中導(dǎo)出許多 React 組件。例如,有一個(gè)名為 MyComponents.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" />;
}
用戶定義組件必須首字母大寫
當(dāng)元素類型以小寫字母開頭時(shí),它表示一個(gè)內(nèi)置的組件,如 <div> 或 <span>,將導(dǎo)致字符串 'div' 或 'span' 傳遞給 React.createElement。 以大寫字母開頭的類型,如 <Foo /> 編譯為 React.createElement(Foo),并且它正對應(yīng)于你在 JavaScript 文件中定義或?qū)氲慕M件。
我們建議用大寫開頭命名組件。如果你的組件以小寫字母開頭,請?jiān)?JSX 中使用之前其賦值給大寫開頭的變量。
例如,下面的代碼將無法按預(yù)期運(yùn)行:
import React from 'react';
// 錯(cuò)誤!組件名應(yīng)該首字母大寫:
function hello(props) {
// 正確!div 是有效的 HTML 標(biāo)簽:
return <div>Hello {props.toWhat}</div>;
}
function HelloWorld() {
// 錯(cuò)誤!React 會(huì)將小寫開頭的標(biāo)簽名認(rèn)為是 HTML 原生標(biāo)簽:
return <hello toWhat="World" />;
}
為了解決這個(gè)問題,我們將 hello 重命名為 Hello,然后使用 <Hello /> 引用:
import React from 'react';
// 正確!組件名應(yīng)該首字母大寫:
function Hello(props) {
// 正確!div 是有效的 HTML 標(biāo)簽:
return <div>Hello {props.toWhat}</div>;
}
function HelloWorld() {
// 正確!React 能夠?qū)⒋髮戦_頭的標(biāo)簽名認(rèn)為是 React 組件。
return <Hello toWhat="World" />;
}
在運(yùn)行時(shí)選擇類型
你不能使用一個(gè)通用的表達(dá)式來作為 React 元素的標(biāo)簽。如果你的確想使用一個(gè)通用的表達(dá)式來確定 React 元素的類型,請先將其賦值給大寫開頭的變量。這種情況一般發(fā)生于當(dāng)你想基于屬性值渲染不同的組件時(shí):
import React from 'react';
import { PhotoStory, VideoStory } from './stories';
const components = {
photo: PhotoStory,
video: VideoStory
};
function Story(props) {
// 錯(cuò)誤!JSX 標(biāo)簽名不能為一個(gè)表達(dá)式。
return <components[props.storyType] story={props.story} />;
}
要解決這個(gè)問題,我們需要先將類型賦值給大寫開頭的變量。
import React from 'react';
import { PhotoStory, VideoStory } from './stories';
const components = {
photo: PhotoStory,
video: VideoStory
};
function Story(props) {
// 正確!JSX 標(biāo)簽名可以為大寫開頭的變量。
const SpecificStory = components[props.storyType];
return <SpecificStory story={props.story} />;
}
JSX的屬性(Props)
在 JSX 中有幾種不同的方式來指定屬性。
使用 JavaScript 表達(dá)式作為屬性
你可以傳遞JavaScript 表達(dá)式作為一個(gè)屬性,再用大括號(hào){}括起來。例如,在這個(gè) JSX 中:
<MyComponent foo={1 + 2 + 3 + 4} />
對于 MyComponent來說, props.foo 的值為 10,這是 1 + 2 + 3 + 4 表達(dá)式計(jì)算得出的。
if 語句和 for 循環(huán)在 JavaScript 中不是表達(dá)式,因此它們不能直接在 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>;
}
你可以在相關(guān)部分中了解有關(guān) 條件渲染 和 循環(huán) 的更多信息。
字符串常量
你可以將字符串常量作為屬性值傳遞。下面這兩個(gè) JSX 表達(dá)式是等價(jià)的:
<MyComponent message="hello world" />
<MyComponent message={'hello world'} />
當(dāng)傳遞一個(gè)字符串常量時(shí),該值為HTML非轉(zhuǎn)義的,所以下面兩個(gè) JSX 表達(dá)式是相同的:
<MyComponent message="<3" />
<MyComponent message={'<3'} />
這種行為通常是無意義的,提到它只是為了完整性。
屬性默認(rèn)為“True”
如果你沒有給屬性傳值,它默認(rèn)為 true。因此下面兩個(gè) JSX 是等價(jià)的:
<MyTextBox autocomplete />
<MyTextBox autocomplete={true} />
一般情況下,我們不建議這樣使用,因?yàn)樗鼤?huì)與 ES6 對象簡潔表示法 混淆。比如 {foo} 是 {foo: foo} 的簡寫,而不是 {foo: true}。這里能這樣用,是因?yàn)樗?HTML 的做法。
展開屬性
如果你已經(jīng)有了個(gè) props 對象,并且想在 JSX 中傳遞它,你可以使用 ... 作為“展開(spread)”操作符來傳遞整個(gè)屬性對象。下面兩個(gè)組件是等效的:
function App1() {
return <Greeting firstName="Ben" lastName="Hector" />;
}
function App2() {
const props = {firstName: 'Ben', lastName: 'Hector'};
return <Greeting {...props} />;
}
You can also pick specific props that your component will consume while passing all other props using the spread operator.
const Button = props => {
const { kind, ...other } = props;
const className = kind === "primary" ? "PrimaryButton" : "SecondaryButton";
return <button className={className} {...other} />;
};
const App = () => {
return (
<div>
<Button kind="primary" onClick={() => console.log("clicked!")}>
Hello World!
</Button>
</div>
);
};
In the example above, the kind prop is safely consumed and is not passed on to the <button> element in the DOM. All other props are passed via the ...otherobject making this component really flexible. You can see that it passes an onClick and children props.
展開屬性非常有用。但是他們也容易傳遞不必要的屬性給組件,而組件并不需要這些多余屬性?;蛘邆鬟f無效的HTML熟悉給DOM。我們建議你謹(jǐn)慎使用此語法。
JSX中的子代
在既包含開始標(biāo)簽又包含結(jié)束標(biāo)簽的 JSX 表達(dá)式中,這兩個(gè)標(biāo)簽之間的內(nèi)容被傳遞為專門的屬性:props.children。有幾種不同的方法來傳遞子代:
字符串字面量
你可以在開始和結(jié)束標(biāo)簽之間放入一個(gè)字符串,則 props.children 就是那個(gè)字符串。這對于許多內(nèi)置 HTML 元素很有用。例如:
<MyComponent>Hello world!</MyComponent>
這是有效的 JSX,并且 MyComponent 的 props.children 值將會(huì)直接是 "hello world!"。因?yàn)?HTML 未轉(zhuǎn)義,所以你可以像寫 HTML 一樣寫 JSX:
<div>This is valid HTML & JSX at the same time.</div>
JSX 會(huì)移除空行和開始與結(jié)尾處的空格。標(biāo)簽鄰近的新行也會(huì)被移除,字符串常量內(nèi)部的換行會(huì)被壓縮成一個(gè)空格,所以下面這些都等價(jià):
<div>Hello World</div>
<div>
Hello World
</div>
<div>
Hello
World
</div>
<div>
Hello World
</div>
JSX子代
你可以提供更多個(gè) JSX 元素作為子代,這對于嵌套顯示組件非常有用:
<MyContainer>
<MyFirstComponent />
<MySecondComponent />
</MyContainer>
你可以混合不同類型的子代,同時(shí)使用字符串字面量和 JSX子代,這是 JSX 類似 HTML 的另一種形式,這在 JSX 和 HTML 中都是有效的:
<div>
Here is a list:
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
</div>
React 組件也可以返回包含多個(gè)元素的一個(gè)數(shù)組:
render() {
// 不需要使用額外的元素包裹數(shù)組中的元素!
return [
// 不要忘記 key :)
<li key="A">First item</li>,
<li key="B">Second item</li>,
<li key="C">Third item</li>,
];
}
JavaScript 表達(dá)式作為子代
你可以將任何 {} 包裹的 JavaScript 表達(dá)式作為子代傳遞。例如,下面這些表達(dá)式是等價(jià)的:
<MyComponent>foo</MyComponent>
<MyComponent>{'foo'}</MyComponent>
這對于渲染任意長度的 JSX 表達(dá)式的列表很有用。例如,下面將會(huì)渲染一個(gè) 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 表達(dá)式可以與其他類型的子代混合使用。這通常對于字符串模板非常有用:
function Hello(props) {
return <div>Hello {props.addressee}!</div>;
}
布爾值、Null 和 Undefined 被忽略
false、null、undefined 和 true 都是有效的子代,只是它們不會(huì)被渲染。下面的JSX表達(dá)式將渲染為相同的東西:
<div />
<div></div>
<div>{false}</div>
<div>{null}</div>
<div>{undefined}</div>
<div>{true}</div>
這在根據(jù)條件來確定是否渲染React元素時(shí)非常有用。以下的JSX只會(huì)在showHeader為true時(shí)渲染<Header />組件。
<div>
{showHeader && <Header />}
<Content />
</div>
一個(gè)告誡是JavaScript中的一些 "falsy" 值(比如數(shù)字0),它們依然會(huì)被React渲染。例如,下面的代碼不會(huì)像你預(yù)期的那樣運(yùn)行,因?yàn)楫?dāng) props.message 為空數(shù)組時(shí),它會(huì)打印0:
<div>
{props.messages.length &&
<MessageList messages={props.messages} />
}
</div>
要解決這個(gè)問題,請確保 && 前面的表達(dá)式始終為布爾值:
<div>
{props.messages.length > 0 &&
<MessageList messages={props.messages} />
}
</div>
相反,如果你想讓類似 false、true、null 或 undefined 出現(xiàn)在輸出中,你必須先把它轉(zhuǎn)換成字符串 :
<div>
My JavaScript variable is {String(myVariable)}.
</div>
使用 PropTypes 進(jìn)行類型檢查
使用 prop-types 庫幫助我們對組件的屬性進(jìn)行類型的控制。
隨著應(yīng)用日漸龐大,你可以通過類型檢查捕獲大量錯(cuò)誤。 對于某些應(yīng)用來說,你還可以使用 Flow 或 TypeScript 這樣的 JavsScript 擴(kuò)展來對整個(gè)應(yīng)用程序進(jìn)行類型檢查。然而即使你不用它們,React 也有一些內(nèi)置的類型檢查功能。要檢查組件的屬性,你需要配置特殊的 propTypes 屬性:
import PropTypes from 'prop-types';
class Greeting extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
Greeting.propTypes = {
name: PropTypes.string
};
PropTypes 包含一整套驗(yàn)證器,可用于確保你接收的數(shù)據(jù)是有效的。在這個(gè)示例中,我們使用了 PropTypes.string。當(dāng)你給屬性傳遞了無效值時(shí),JavsScript 控制臺(tái)將會(huì)打印警告。出于性能原因,propTypes只在開發(fā)模式下進(jìn)行檢查。
PropTypes
下面是使用不同驗(yàn)證器的例子:
import PropTypes from 'prop-types';
MyComponent.propTypes = {
// 你可以將屬性聲明為以下 JS 原生類型
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,
// 任何可被渲染的元素(包括數(shù)字、字符串、子元素或數(shù)組)。
optionalNode: PropTypes.node,
// 一個(gè) React 元素
optionalElement: PropTypes.element,
// 你也可以聲明屬性為某個(gè)類的實(shí)例,這里使用 JS 的
// instanceof 操作符實(shí)現(xiàn)。
optionalMessage: PropTypes.instanceOf(Message),
// 你也可以限制你的屬性值是某個(gè)特定值之一
optionalEnum: PropTypes.oneOf(['News', 'Photos']),
// 限制它為列舉類型之一的對象
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),
// 一個(gè)指定元素類型的數(shù)組
optionalArrayOf: PropTypes.arrayOf(PropTypes.number),
// 一個(gè)指定類型的對象
optionalObjectOf: PropTypes.objectOf(PropTypes.number),
// 一個(gè)指定屬性及其類型的對象
optionalObjectWithShape: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
}),
// 你也可以在任何 PropTypes 屬性后面加上 `isRequired`
// 后綴,這樣如果這個(gè)屬性父組件沒有提供時(shí),會(huì)打印警告信息
requiredFunc: PropTypes.func.isRequired,
// 任意類型的數(shù)據(jù)
requiredAny: PropTypes.any.isRequired,
// 你也可以指定一個(gè)自定義驗(yàn)證器。它應(yīng)該在驗(yàn)證失敗時(shí)返回
// 一個(gè) Error 對象而不是 `console.warn` 或拋出異常。
// 不過在 `oneOfType` 中它不起作用。
customProp: function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
return new Error(
'Invalid prop `' + propName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
},
// 不過你可以提供一個(gè)自定義的 `arrayOf` 或 `objectOf`
// 驗(yàn)證器,它應(yīng)該在驗(yàn)證失敗時(shí)返回一個(gè) Error 對象。 它被用
// 于驗(yàn)證數(shù)組或?qū)ο蟮拿總€(gè)值。驗(yàn)證器前兩個(gè)參數(shù)的第一個(gè)是數(shù)組
// 或?qū)ο蟊旧恚诙€(gè)是它們對應(yīng)的鍵。
customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
if (!/matchme/.test(propValue[key])) {
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
})
};
限制單個(gè)子代
使用 PropTypes.element 你可以指定只傳遞一個(gè)子代
import PropTypes from 'prop-types';
class MyComponent extends React.Component {
render() {
// This must be exactly one element or it will warn.
const children = this.props.children;
return (
<div>
{children}
</div>
);
}
}
MyComponent.propTypes = {
children: PropTypes.element.isRequired
};
屬性默認(rèn)值
你可以通過配置 defaultProps 為 props定義默認(rèn)值:
class Greeting extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
// 為屬性指定默認(rèn)值:
Greeting.defaultProps = {
name: 'Stranger'
};
// 渲染 "Hello, Stranger":
ReactDOM.render(
<Greeting />,
document.getElementById('example')
);
如果你在使用像 transform-class-properties 的 Babel 轉(zhuǎn)換器,你也可以在React 組件類中聲明 defaultProps 作為靜態(tài)屬性。這個(gè)語法還沒有最終通過,在瀏覽器中需要一步編譯工作。更多信息,查看類字段提議。
class Greeting extends React.Component {
static defaultProps = {
name: 'stranger'
}
render() {
return (
<div>Hello, {this.props.name}</div>
)
}
}
defaultProps 用來確保 this.props.name 在父組件沒有特別指定的情況下,有一個(gè)初始值。類型檢查發(fā)生在 defaultProps 賦值之后,所以類型檢查也會(huì)應(yīng)用在 defaultProps 上面。