React從入門到精通系列之(12)深入理解JSX

張亞濤發表於2016-12-15

十二、深入理解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=`&lt;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標籤 &amp; 同時也是個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在渲染時會被自動忽略

falsenullundefinedtrue是有效的子元素,不過他們從根本上講是不參與渲染的。 這些JSX表示式將渲染處相同的東西:

<div />

<div></div>

<div>{false}</div>

<div>{null}</div>

<div>{true}</div>

這對於有條件地呈現React元素很有用。 如果showHeadertrue,那麼這個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>

相反,如果你想要一個值如falsetruenullundefined出現在輸出中,你必須先將它轉換為字串

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`)
);

相關文章