十二、深入理解JSX
從根本上講,JSX就是提供了一個React.createElement(component, props, ...children)
函式的語法糖。就像下面的JSX程式碼:
<MyButton color="blue" shadow={2}>
Click Me
</MyButton>
經過編譯後為:
React.createElement(
MyButton,
{color: `blue`, shadow: 2},
`Click Me`
)
如果一個標籤沒有子元素的話,你可以使用/>
來自動閉合。例如:
<div className="sidebar" />
經過編譯後為:
React.createElement(
`div`,
{className: `sidebar`},
null
)
如果你想測試一些特定的JSX是如何轉換成JavaScript的話,你可以試試線上Babel編譯器
。
指定React元素型別
JSX標記的第一部分決定了React元素的型別。
首字母大寫的型別表示JSX標記指的為React元件。 這些標籤被編譯為對指定變數的直接引用,因此如果使用JSX <Foo />
表示式,Foo必須在當前的作用域內。
React必須在作用域內
由於JSX編譯的本質是對React.createElement
的呼叫,因此React庫也必須始終在JSX程式碼的作用域中。
例如,雖然CustomButton
沒有直接引用React
,但是這兩個匯入的模組在這段程式碼中也還是很有必要的:
import React from `react`;
import ReactDOM from `react-dom`;
function WarningButton(props) {
// return React.createElement(CustomButton, {color: `red`}, null);
return <CustomButton color="red" />
}
如果不使用JavaScript打包工具並將React通過script標籤引入,那麼它就會作為一個全域性變數React
。
對JSX型別使用『點』表示符
您還可以使用JSX中的點表示符來引用React元件。 如果您有一個模組會匯出很多React元件的話,使用這種方法就會十分方便。 例如,如果MyComponents.DatePicker
是一個元件,您可以直接從JSX使用它:
import React from `react`;
import ReactDOM from `react-dom`;
const MyComponents = {
DatePicker(props) {
return <div>這裡有一個顏色為{props.color}的日期選擇器</div>
}
};
function BlueDataPicker(props) {
return <MyComponents.DatePicker color="blue" />
}
ReactDOM.render(
<BlueDataPicker />,
document.getElementById(`root`)
);
使用者自定義元件必須是首字母大寫
當元素型別以是小寫字母開頭時,它指向一個內建元件,如<div>
或<span>
,並生成一個字串`div`
或`span`
傳遞給React.createElement
。 以大寫字母開頭的型別,如<Foo />
編譯為React.createElement(Foo)
,並且在當前作用域內尋找這個名稱為Foo
的已定義或已匯入元件。
我們建議使用首字母大寫
命名元件。 如果你有一個以小寫字母開頭的元件,請在JSX中使用它之前請將它賦值給一個首字母大寫的變數。
下面程式碼不會按預期執行:
import React from `react`;
//這是錯誤的,這個元件應該為首字母大寫
function hello(props) {
// 這是正確的,因為div是一個有效的html標籤
return <div>Hello {props.name}</div>;
}
function HelloWorld(props) {
// 這是錯誤的,因為它是首字母小寫,所以React認為<hello />是一個html標籤
return <hello name="zhangyatao" />
}
想要修復上面的問題,我們必須將hello
重新命名為Hello
,通過<Hello />
來使用該元件:
import React from `react`;
// 這是正確的
function Hello(props) {
return <div>Hello {props.name}</div>;
}
function HelloWorld(props) {
// 這是正確的
return <Hello name="zhangyatao" />;
}
在執行的時候選擇元件型別
不能將常規的javascript表示式用作React元素型別。 如果你想使用一個通用表示式來表示元素的型別,只需將它賦值給一個首字母大寫的變數即可。
這通常出現在當你想基於同一個props渲染一個不同的元件的情況下:
import React from `react`;
import {Com1, Com2} from `./Components`;
const components = {
myCom1: Com1,
myCom2: Com2
}
function RunCom(props) {
// 這是錯誤的,JSX的型別不能這麼寫
return <components[props.comType] type={props.type} />;
}
想要解決上面的問題,只需要將它們賦值給一個首字母大寫的變數即可:
import React from `react`;
import {Com1, Com2} from `./Components`;
const components = {
myCom1: Com1,
myCom2: Com2
}
function RunCom(props) {
// 這是正確的,將它們賦值給一個首字母大寫的變數
const MyCom = components[props.comType];
return <MyCom type={props.type} />;
}
JSX中的Props
在JSX中指定Props有以下幾種不同的方法。
JavaScript表示式
你可以傳遞任何JavaScript表示式作為Props,用{}
括住它們就可以使用。 例如,在這個JSX中:
<MyComponents foo={1 + 2 + 3 + 4} />
對於MyComponent
來說,props.foo
的值將為10
,因為是通過表示式1 + 2 + 3 + 4
計算得到的。
if
語句和for
迴圈在JavaScript中不是表示式,因此它們不能在JSX中直接使用。 相反,寫完它們之後你可以把JSX放在裡面。 例如:
function NumberDescriber(props) {
let description;
if (props.number % 2 === 0) {
description = <strong>偶數</strong>
} else {
description = <strong>奇數</strong>
}
return <div>{props.number}是一個{description}.</div>;
}
字串直接量
你可以傳遞一個字串內容作為props。 這兩個JSX表示式是等價的:
<MyComponent message="hi zhangyatao" />
<MyComponent message={`hi zhangyatao`} />
當你傳遞一個字串直接量時,它的值是經過html轉義的。 所以這兩個JSX表示式是等價的:
<MyComponent message=`<3` />
<MyComponent message={`<3`} />
Props預設值為true
如果你沒有給Props傳入一個值,那麼它的預設值為true
,這兩個JSX表示式是等價的:
<MyTextBox autocomplete />
<MyTextBox autocomplete={true} />
一般來說,我們不建議使用它,因為它可以使用ES6物件的簡寫{foo}
,也就是{foo:foo}
的簡稱會和{foo:true}
混淆。 這種行為在這裡只是方便它匹配到HTML行為。
Props傳遞
如果你有一個物件類似的資料作為props,並且想在JSX中傳遞它,你可以使用...
作為一個“spread”
運算子傳遞整個props物件。 這兩個元件是等效的:
function App() {
return <Greeting firstName="yatao" lastName="zhang" />;
}
function App() {
const props = {firstName: `yatao`, lastName: `zhang`};
return <Greeting {...props} />;
}
當建立一個通用容器時,spread
props很有用。
然而,他們也可以讓你的程式碼變得有點凌亂,這樣很容易使大量不相關的prps傳遞給那些不關心它們的元件。 建議您謹慎使用此語法。
JSX中的子元素和子元件
在包含開始標記和結束標記的JSX表示式中,這些標記之間的內容通過一種特殊的prop:props.children
傳遞。 有幾種不同的方式傳遞子元件:
字串直接量
你可以在開始和結束標籤之間放一個字串,那麼props.children
就是那個字串。 這對許多內建的HTML元素很有用。 例如:
function MyComponent(props) {
return <div>{props.children}<div>; //=> <div>hello zhangyatao</div>
}
<MyComponent>Hello zhangyatao</MyComponent>
這是有效的JSX,並且MyComponent
中的props.children
將是字串“Hello zhangyatao”
。 HTML標籤是不會經過轉義的,所以你一般可以寫JSX就像你寫HTML一樣:
<div>這是一個html標籤 & 同時也是個JSX</div>
JSX會刪除行的開始和結尾處的空格。 它也會刪除中間的空行。 與標籤相鄰的空行被會被刪除;
在字串文字中間出現的空行會縮合成一個空格。 所以這些都渲染相同的事情:
<div>hello zhangyatao</div>
<div>
hello zhangyatao
</div>
<div>
hello
zhangyatao
</div>
<div>
hello zhangyatao
</div>
JSX子元素
你可以使用很多個JSX元素作為子元素。 這對需要巢狀的顯示型別元件很有用:
<Dialog>
<DialogHeader />
<DialogBody />
<DialogFooter />
</Dialog>
你可以將不同型別的子元素混合在一起,因此JSX子元素可以與字串直接量一起使用。 這是JSX的另一種方式,就像一個HTML一樣:
<div>
這是一個列表
<ul>
<li>item 1</li>
<li>item 2</li>
</ul>
</div>
一個React元件不可能返回多個React元素,但是一個JSX表示式可以包含多個子元素,因此如果你想讓一個元件渲染多個東西,你可以將它們統一放置在就像上面那樣的div中。
Javascript表示式
您可以將任何JavaScript表示式放在{}
中作為子元件傳遞。 例如,下面這些表示式是等價的:
function MyComponent(props) {
return <div>{props.children}<div>; //=> <div>hi zhangyatao</div>
}
<MyComponent>hi zhangyatao</MyComponent>
<MyComponent>{`hi zhangyatao`}</MyComponent>
這通常用於渲染任意長度的JSX表示式列表。 例如,這將渲染一個HTML列表:
function Item(props) {
return <li>{props.message}</li>;
}
function TodoList(props) {
const todos = [`完成文件`, `出去逛街`, `打一局dota`];
return (
<ul>
{todos.map(message => <Item key={message} message={message} />)}
</ul>
);
}
JavaScript表示式可以與其他型別的子元素混合使用。 這通常用於替換字串模板:
function Hello(props) {
return <div>Hello {props.name}</div>;
}
使用函式作為子元素
通常,插入JSX中的JavaScript表示式都最終返回為一個字串、React元素、一個列表。
當然,props.children
可以像任何其他props那樣工作,它可以傳遞任何型別的資料,並不侷限於那些告訴React應該如何渲染的東東。 例如,如果您有一個自定義元件,您可以將props.children
作為一個回撥函式:
import React from `react`;
import ReactDOM from `react-dom`;
function Repeat(props) {
let items = [];
let callback = props.children;
var numTimes = props.numTimes;
for(var i = 0 ; i < numTimes ; i++ ){
items.push(callback(i));
}
return <div>{items}</div>;
}
function ListOfTenThings(props) {
return (
<Repeat numTimes={10}>
{index => <div key={index}>這是列表中的第{index}項</div>}
</Repeat>
);
}
ReactDOM.render(
<ListOfTenThings/>,
document.getElementById(`root`)
);
傳遞給自定義元件的子元素可以是任何東西,只要在React在渲染之前,該元件將它們轉換為可以理解的東西即可。 這種用法並不常見,如果你想擴充套件JSX的其他能力,可以通過這個例子瞭解下它的工作原理。
布林值、null、undefined在渲染時會被自動忽略
false
,null
,undefined
和true
是有效的子元素,不過他們從根本上講是不參與渲染的。 這些JSX表示式將渲染處相同的東西:
<div />
<div></div>
<div>{false}</div>
<div>{null}</div>
<div>{true}</div>
這對於有條件地呈現React元素很有用。 如果showHeader
為true
,那麼這個JSX只渲染一個<Header />
:
<div>
{showHeader && <Header />}
<Content />
</div>
如果返回一些“假的”
值就會收到一個警告,如數字0
,不過React仍然會渲染。 例如,此程式碼將不會像您預期的那樣工作,因為當props.messages
是空陣列時將列印0
:
<div>
{props.messages.length && <Message messages={props.messages} />}
</div>
想要修復上面的問題,你要確定這個表示式在&&之前總返回布林值
:
<div>
{props.messages.length > 0 && <Message messages={props.messages} />}
</div>
相反,如果你想要一個值如false
,true
,null
或undefined
出現在輸出中,你必須先將它轉換為字串
:
import React from `react`;
import ReactDOM from `react-dom`;
function MyVariable(props) {
const myVariable = false;
// 如果這裡不把false轉換為字串,這隻會輸出『我的javascript變數是』
const convertedVar = String(myVariable);
return (
<div>
我的javascript變數是{convertedVar}
</div>
);
}
ReactDOM.render(
<MyVariable/>,
document.getElementById(`root`)
);