JSX語法詳解

一、基礎
1、JSX是什麼
JSX是一種像下面這樣的語法:

const element = <h1>Hello, world!</h1>;
1
它是一種JavaScript語法擴展,在React中可以方便地用來描述UI。

本質上,JSX為我們提供了創建React元素方法(React.createElement(component, props, …children))的語法糖(syntactic sugar)。上面的程式碼實質上等價於:

var element = React.createElement(
“h1”,
null,
“Hello, world!”
);

2、JSX代表JS對象
JSX本身也是一個表達式,在編譯後,JSX表達式會變成普通的JavaScript對象。

你可以在if語句或for循環中使用JSX,你可以將它賦值給變數,你可以將它作為參數接收,你也可以在函數中返回JSX。

例如下面的程式碼:

function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>;

上面的程式碼在if語句中使用JSX,並將JSX作為函數返回值。實際上,這些JSX經過編譯後都會變成JavaScript對象。

經過babel會變成下面的js程式碼:

function test(user) {
if (user) {
return React.createElement(
“h1”,
null,
“Hello, “,
formatStr(user),
“!”
);
}
return React.createElement(
“h1”,
null,
“Hello, Stranger.”
);
}

3、在JSX中使用JavaScript表達式
在JSX中插入JavaScript表達式十分簡單,直接在JSX中將JS表達式用大括弧括起來即可。例如:

function formatName(user) {
return user.firstName + ‘ ‘ + user.lastName;
}

const user = {
firstName: ‘Harper’,
lastName: ‘Perez’
};

const element = (
<h1>
Hello, {formatName(user)}!
</h1>
);

ReactDOM.render(
element,
document.getElementById(‘root’)
);

上面的程式碼中用到了函數調用表達式fromatName(user)。

在JavaScript中,表達式就是一個短語,Javascript解釋器會將其計算出一個結果,常量就是最簡單的一類表達式。常用的表達式有:

變數名;
函數定義表達式;
屬性訪問表達式;
函數調用表達式;
算數表達式;
關係表達式;
邏輯表達式;
需要注意的是,if語句以及for循環不是JavaScript表達式,不能直接作為表達式寫在{}中,但可以先將其賦值給一個變數(變數是一個JavaScript表達式):

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>;
}

4、JSX屬性值
你可以使用引號將字元串字面量指定為屬性值:

const element = <div tabIndex=”0″></div>;

注意這裡的」0」是一個字元串字面量。

或者你可以將一個JavaScript表達式嵌在一個大括弧中作為屬性值:

const element = <img src={user.avatarUrl}></img>;

這裡用到的是JavaScript屬性訪問表達式,上面的程式碼將編譯為:

const element = React.createElement(“img”, { src: user.avatarUrl });

5、JSX的Children
首先JSX可以是一個不包含Children的empty tag。如:

const element = <img src={user.avatarUrl} />;
1
JSX也可以像HTML標籤一樣包含Children:

const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);

這種寫法在生成React元素的時候給我們帶來了很大的便利,而且能夠更加直觀地描述UI。不然我們需要像下面這樣創建和上面程式碼等價的React元素:

const element = React.createElement(
“div”,
null,
React.createElement(
“h1”,
null,
“Hello!”
),
React.createElement(
“h2”,
null,
“Good to see you here.”
)
);

tip: React DOM結點使用駱駝拼寫法給屬性命名

例如:class在JSX中應寫作className,tabindex應寫作tabIndex。

另外關於JSX的children需要注意的是:

React自定義組件的chilren是不會像固有的HTML標籤的子元素那樣自動render的,我們看下面的例子:

程式碼1
class Test extends React.Component {
render() {
return (
<div>
Here is a list:
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
</div>
)
}
};
ReactDOM.render(
<Test />,
document.getElementById(‘test’)
);

以上程式碼定義的組件中都是build-in組件,類似div、p、ul、li等。它們中的子元素會直接render出來,像下面這樣:

 

但是如果你使用用戶定義組件,比如:

class Test extends React.Component {
render() {
return (
<Em>
Here is a list:
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
</Em>
)
}
};

class Em extends React.Component {
render() {
return (<div></div>);
}
}

ReactDOM.render(
<Test />,
document.getElementById(‘test’)
);

並不能得到跟上面程式碼1一樣的結果,我們得到的只是一個空的div標籤:

 

如果你想得到和程式碼1一樣的結果,需要顯示地指定props.children,像下面這樣:

class Test extends React.Component {
render() {
return (
<Em>
Here is a list:
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
</Em>
)
}
};

class Em extends React.Component {
render() {
return (<div>{this.props.children}</div>);
}
}

ReactDOM.render(
<Test />,
document.getElementById(‘test’)
);

得到下面的結果:

 

6、JSX可自動防範注入攻擊
在JSX中嵌入接收到的內容是安全的,比如:

const danger = response.potentialDanger;

cosnt ele = <h1>{title}</h1>

在默認情況下,React DOM會將所有嵌入JSX的值進行編碼。這樣可以有效避免xss攻擊。

我們將以下程式碼編譯後引入html:

class Test extends React.Component {
render() {
let v = “<script><\/script>”;
return (
<div>
<h1>{v}</h1>
</div>
)
}
};

ReactDOM.render(
<Test />,
document.getElementById(‘test’)
);

得到結果是:

 

可以看到通過JSX插入的文本自動進行了HTML轉義,所以這裡插入的是一段文本,而不是<script>標籤。這有點類似於Js中的document.createTextNode(“…”)(實際上我們可以利用document.createTextNode進行HTML轉義)。

作為對比,換作使用DOM元素的innerHTML屬性:

<!DOCTYPE html>
<html>
<head>
<meta charset=”utf-8″>
</head>
<body>
<div id=”test”></div>
<script type=”text/javascript”>
document.getElementById(“test”).innerHTML=”<h1><script><\/script><\/h1>”;
</script>
</body>
</html>

得到結果如下:

 

注意文本的顏色,此時插入的是一個<script>標籤。

如果你很清楚自己在做什麼,希望直接將字元串不經轉義編碼直接插入到HTML文檔流,可以使用dangerouslySetInnerHTML屬性,這是一個React版的innerHTML,該屬性接收一個key為__html的對象,修改程式碼如下:

class Test extends React.Component {
render() {
let v = {
__html: “<script><\/script>”
};
return (
<div>
<h1 dangerouslySetInnerHTML={v}/>
</div>
)
}
};

這次我們插入了<script>標籤:

 

二、進階
1、JSX中的props
指定JSX中的props有以下幾種方式:

(1)使用JavaScript表達式
任何有效的JavaScript表達式都可以作為prop的值,使用的時候將該表達式放在一對大括弧中即可:

<MyComponent foo={1 + 2 + 3 + 4} />

<YourComponent clickTodo={(id) => this.props.handleClick(id)} />

(2)使用字元串字面量
字元串字面量可以作為prop值,下面的程式碼是等價的:

<MyComponent message=”hello world” />

<MyComponent message={‘hello world’} />

(3)使用擴展運算符
如果你想將一個prop對象傳入JSX,你可以使用擴展運算符…直接將整個prop對象傳入。下面的2個組件是等價的:

function App1() {
return <Greeting firstName=”Ben” lastName=”Hector” />;
}

function App2() {
const props = {firstName: ‘Ben’, lastName: ‘Hector’};
return <Greeting {…props} />;
}

擴展運算符是一個es6特性。是一種傳遞屬性的十分便利的方式。但請注意不要濫用該運算符,注意不要將一大堆毫不相關的prop一股腦全部傳入下面的組件中。

2、JSX中的Children
React組件中有一個特殊的prop–props.children。它指代了JSX表達式中開閉標籤中包含的內容。

下面討論的是幾種指定JSX的children的方法:

(1)使用字元串字面量
你可以直接在JSX的開閉標籤中寫入字元串字面量,組件得到的props.children就是該字元串值。

以下面的程式碼為例:

<MyComponent>Hello world!</MyComponent>
1
MyComponent的props.chilren將獲得」Hello World!」字元串。通過該方式傳入的字元串是未經HTML轉義的。實際上你只需要像在HTML標籤中寫入文本那樣就可以了。例如你想在一對<p>標籤中寫入文本」<script></script>」,HTML和JSX寫法是一樣的,就像下面這樣:

<p>&#60;script&#62;&#60;/script&#62;</p>
1
另外需要注意的是:

JXS會自動刪除一行中開頭和結尾處的空白符;JSX會自動刪除空行;JSX會刪除緊鄰標籤的換行;JSX會刪除字元串中的換行;字元串中的換行會被轉換成一個空格。

舉例來說,下面的JSX程式碼都是等價的:

<div>Hello World</div>

<div> Hello World </div>

<div>
Hello World
</div>

<div>
Hello
World
</div>

<div>

Hello World
</div>

(2)JSX元素作為children
我們同樣可以使用JSX元素作為JSX的children,由此生成嵌套組件:

<MyContainer>
<MyFirstComponent />
<MySecondComponent />
</MyContainer>

我們也可以混合使用字元串字面量和JSX作為children:

<El>
Here is a list:
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
</El>

El的props.children將得到一個數組:

 

可以看到數組的第一個元素就是字元串「Here is a list:」,第二個元素是一個對象(JSX代表JavaScript對象)。

(3)JavaScript表達式
和prop一樣,你也可以將任何有效的JavaScript表達式作為children傳入,將它放在{}中就可以了。像下面這樣:

<MyComponent>{‘foo’}</MyComponent>

這裡傳入了一個常量表達式。

下面使用一個函數調用表達式來生成一個list作為children:

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

當然你也可以在一個字元串children中插入一個JavaScript表達式來生成一個「模板」:

function Hello(props) {
return <div>Hello {props.username}!</div>;
}

(4)函數children
首先說明,這不是一種常見的用法。

實際上,傳入自定義組件的children並沒有嚴格的限制,只要在React需要render的時候能將它們轉換成可以render的東西就行了。

下面是一個函數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>;
}

實際上,我們更通常的情況下是將(index) => <div key={index}>This is item {index} in the list</div>作為一個prop傳入子組件。這個例子只是作為一種理解上的擴展。

(5)有關布爾值、Null以及Undefined
布爾值,Null以及Undefined可以作為有效的children,但他們不會被render,下面的JSX表達式都會render一個空的div標籤:

<div />

<div></div>

<div>{false}</div>

<div>{null}</div>

<div>{true}</div>

關於此有一個有趣的應用,在條件render中,下面的<Header />只有在show為true時才會render:

<div>
{showHeader && <Header />}
<Content />

3、注意事項
(1)使用JSX時要引入React庫
前面已經解釋過了,JSX是React.createElement方法的語法糖,因此在使用JSX的作用域中必須引入React庫。

如果你使用了JS打包工具,你可以在文件的頭部作如下引用:

import React from ‘react’;
1
或者你不使用打包工具,也可以直接通過script標籤引入React,比如:

//本地
<script src=”./react.js”></script>

//或者BootCDN
<script src=”//cdn.bootcss.com/react/15.4.0/react.js”></script>

此時React將作為一個全局變數被引入,變數名就是』React』。

(2)注意引入JSX中用到的自定義組件
JSX中用到的組件可能並不會在JavaScript中直接引用到,但自定義組件本質上就是一個JS對象,你在JSX中使用的時候,需要首先將該組件引入到當前作用域:

import MyComponent from ‘./MyComponent.js’

<Outer>
<MyComponent />
</Outer>

(3)自定義組件首字母一定要大寫
JSX中小寫字母開頭的element代表HTML固有組件如div,span,p,ul等。用戶自定義組件首字母一定要大寫如<Header>、<Picker>。

(4)元素標籤名不能使用表達式
下面的程式碼將產生錯誤:

const components = {
photo: PhotoStory,
video: VideoStory
};

function Story(props) {
// Wrong! JSX標籤名不能使用表達式
return <components[props.storyType] story={props.story} />;
}

如果你需要使用一個表達式來決定元素標籤,你應該先將該表達式的值賦給一個大寫字母開頭的變數:

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} />;
}

(5)設置style屬性
在設置標籤style屬性的時候,要注意,我們是將一個描述style的對象以JavaScipt表達式的形式傳入。因此應該有2層大括弧:

<div style={{color:’red’, margin:’10px auto’}}></div>

 

Tags: