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

12、深刻理解JSX

從根本上講,JSX就是提供了一個React.createElement(component, props, ...children)函數的語法糖。就像下面的JSX代碼:javascript

<MyButton color="blue" shadow={2}>
    Click Me
</MyButton>

通過編譯後爲:html

React.createElement(
    MyButton,
    {color: 'blue', shadow: 2},
    'Click Me'
)

若是一個標籤沒有子元素的話,你可使用/>來自動閉合。例如:java

<div className="sidebar" />

通過編譯後爲:react

React.createElement(
    'div',
    {className: 'sidebar'},
    null
)

若是你想測試一些特定的JSX是如何轉換成JavaScript的話,你能夠試試在線Babel編譯器數組

指定React元素類型

JSX標記的第一部分決定了React元素的類型。dom

首字母大寫的類型表示JSX標記指的爲React組件。 這些標籤被編譯爲對指定變量的直接引用,所以若是使用JSX <Foo />表達式,Foo必須在當前的做用域內。ide

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')
);
相關文章
相關標籤/搜索