React 初窺:JSX 詳解

JSX

我們在上文中已經很多次的提及了 JSX,大家也對於基本的基於 JSX 編寫 React 組件所有了解。實際上在 JSX 推出之初飽受非議,很多人覺得其很怪異。

的確雖然與正統的 HTML 相比其都是類 XML語法的聲明式標籤語言,但是其對於類名強制使用 className、強制要求標籤閉合等特點會讓不少的傳統前端開發者不太適應。

JSX 的引入對筆者之前的工作流的衝擊在於不能夠直接使用 UI 部門提供的頁面模板,並且因為組件化的分割與預編譯,UI 比較麻煩地直接在瀏覽器開發工具中調整CSS樣式然後保存到源代碼中。

JSX 本質上還是屬於 JavaScript,這就避免了我們重複地學習不同框架或庫中的指令約定,而可以直接使用 JavaScript 來描述模板渲染邏輯;而在前端框架的工作流中,往往將 JSX 的轉化工作交託於 Babel 等轉化工具,我們可以通過如下方式指定 JSX 使用的構建函數:

/** @jsx h */

JSX 的前世今生

JSX 語言的名字最早出現在遊戲廠商 DeNA,不過其偏重於加入增強語法使得JavaScript 變得更快、更安全、更簡單。

而 React 則是依賴於 ECMAScript 語法本身,並沒有添加擴充語義。

React 引入 JSX 主要是為了方便 View 層組件化,承載了構建 HTML 結構化頁面的職責。這一點與其他很多的 JavaScript 模板語言異曲同工,不過React 將 JSX 映射為虛擬元素,並且通過創建與更新虛擬元素來管理整個 Virtual DOM 系統。

譬如我們 JSX 語法聲明某個虛擬組件時,會被轉化為React.createElement(component,props,...children) 函數調用,譬如我們定義了某個MyButton:

// 必須要在 JSX 聲明文件中引入 React
import React from 'react';
<mybutton>
Click Me
/<mybutton>

會被編譯為:

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

而如果我們直接聲明某個DOM元素,同樣會轉化為createElement函數調用:

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

實際上除了最著名的 Babel JSX 轉換器之外,我們還可以使用 JSXDOM 與 Mercury JSX 這兩個同樣的可以將 JSX 語法轉化為 DOM 或者 Virtual DOM。

在 JSXDOM 中,只支持使用 DOM 元素,允許在 DOM 標籤中直接使用 JavaScript 變量,譬如當我們需要聲明某個列表時,可以使用如下語法:

/** @jsx JSXDOM */

var defaultValue = "Fill me ...";

document.body.appendChild(


<button>Click Me!/<button>

    {['un', 'deux', 'trois'].map(function(number) {
    return
  • {number}
  • ;
    })}


);

這裡我們還想討論另一個問題,為什麼需要引入 JSX。在 ECAMScript 6 的 ECMA-262 標準中引入了所謂的模板字符串(Template Literals),即可以在 ECMAScript 中使用內嵌的 DSL 來引入 JavaScript 變量,不過雖然模板字符串對於較長的嵌入式 DSL 作用極佳,但是對於需要引入大量作用域中的 ECMAScript 表達式會造成大量的噪音副作用,譬如如果我們要聲明某個評論框佈局,使用 JSX 的方式如下:

// JSX
var box =

{
shouldShowAnswer(user) ?
<answer>no/<answer> :
<box.comment>
Text Content
/<box.comment>
}
;

而使用模板字符串的方式如下:

// Template Literals
var box = jsx`

${
shouldShowAnswer(user) ?
jsx`no${Answer}>` :
jsx`

Text Content
${Box.Comment}>
`
}
${Box}>
`;

其主要缺陷在於因為存在變量的嵌套,需要在作用域中進進出出,很容易造成語法錯誤,因此還是 JSX 語法為佳。


JSX 語法

JSX 的官方定義是類 XML 語法的 ECMAscript 擴展,完美地利用了 JavaScript 自帶的語法和特性,並使用大家熟悉的 HTML 語法來創建虛擬元素。

JSX 基本語法基本被 XML 囊括了,但也有很多的不同之處。React 在定義標籤時,標籤一定要閉合,否則無法編譯通過。

這一點與標準的 HTML 差別很大,HTML 在瀏覽器渲染時會自動進行補全,而強大的 JSX 報錯機制則直接在編譯階段就以報錯的方式指明出來。

HTML 中自閉合的標籤(如 )在 JSX 中也遵循同樣規則,自定義標籤可以根據是否有子組件或文本來決定閉合方式。另外 DOCTYPE 頭也是一個非常特殊的標誌,一般會在使用 React 作為服務端渲染時用到。在 HTML 中,DOCTYPE 是沒有閉合的,也就是說我們無法直接渲染它。常見的做法是構造一個保存 HTML 的變量,將 DOCTYPE 與整個 HTML 標籤渲染後的結果串聯起來。使用JSX聲明組件時,最外層的組件根元素只允許使用單一根元素。因為 JSX 語法會被轉化為 React.createElement(component,props,...children) 調用,而該函數的第一個參數只允許傳入單元素,而不允許傳入多元素。


變量使用

註釋

在 HTML 中,我們會使用 進行註釋,不過 JSX 中並不支持:

render() {
return (



)
}

我們需要以 JavaScript 中塊註釋的方式進行註釋:

{/* A JSX comment */}
{/*
Multi
line
comment
*/}

數組

JSX 允許使用任意的變量,因此如果我們需要使用數組進行循環元素渲染時,直接使用 map、reduce、filter 等方法即可:

function NumberList(props) {
const numbers = props.numbers;
return (

    {numbers.map((number) =>
    <listitem> value={number} />
    )}
    /<listitem>

);
}

條件渲染

在JSX中我們不能再使用傳統的if/else條件判斷語法,但是可以使用更為簡潔明瞭的Conditional Operator運算符,譬如我們要進行if操作:

{condition && 為真時進行渲染 }

如果要進行非操作:

{condition || 為假時進行渲染 }

我們也可以使用常見的三元操作符進行判斷:

{condition
? 為真時進行渲染
: 為假時進行渲染
}

如果對於較大的代碼塊,建議是進行換行以提升代碼可讀性:

{condition ? (

為假時進行渲染

) : (

為假時進行渲染

)}

元素屬性

style 屬性

JSX 中的 style 並沒有跟 HTML 一樣接收某個 CSS 字符串,而是接收某個使用 camelCase 風格屬性的 JavaScript 對象,這一點倒是和DOM 對象的 style 屬性一致。譬如:

const divStyle = {
color: 'blue',
backgroundImage: 'url(' + imgUrl + ')',

};
function HelloWorldComponent() {
return
Hello World!
;
}

注意,內聯樣式並不能自動添加前綴,這也是筆者不太喜歡使用 CSS-in-JS 這種形式設置樣式的的原因。為了支持舊版本瀏覽器,需要提供相關的前綴:

const divStyle = {
WebkitTransition: 'all', // note the capital 'W' here
msTransition: 'all' // 'ms' is the only lowercase vendor prefix
};
function ComponentWithTransition() {
return
This should work cross-browser
;
}

className

React 中是使用 className 來聲明 CSS 類名,這一點對於所有的 DOM 與 SVG 元素都起作用。不過如果你是將 React 與 Web Components 結合使用,也是可以使用 class 屬性的。

htmlFor

因為 for 是JavaScript中的保留關鍵字,因此 React 元素是使用 htmlFor 作為替代。

Boolean 系列屬性

HTML 表單元素中我們經常會使用 disabled、required、checked 與 readOnly 等 Boolean 值性質的書,缺省的屬性值會導致 JSX 認為 bool 值設為 true。當我們需要傳入 false 時,必須要使用屬性表達式。譬如 可以簡寫為,而 即不可以省略 checked 屬性。

自定義屬性

如果在 JSX 中向 DOM 元素中傳入自定義屬性,React 是會自動忽略的:


不過如果要使用HTML標準的自定義屬性,即以 data-* 或者 aria-* 形式的屬性是支持的。



子元素

JSX 表達式中允許在一對開放標籤或者閉合標籤之間包含內容,這即是所謂的子元素,本部分介紹 JSX 支持的不同類別的子元素使用方式。

字符串

我們可以將字符串放置在一對開放與閉合的標籤之間,此時所謂的 props.children 即就是字符串類型;譬如:

<mycomponent>Hello World!/<mycomponent>

就是合法的 JSX 聲明,此時 MyComponent 中的 props.children 值就是字符串 Hello World!;另外需要注意的是,JSX 會自動移除行首與行末的空格,並且移除空行,因此下面的三種聲明方式渲染的結果是一致的:

Hello World


Hello World


Hello
World


Hello World

JSX 嵌套我們可以嵌套地使用 JSX,即將某些 JSX 元素作為子元素,從而允許我們方便地展示嵌套組件:

<mycontainer>
<myfirstcomponent>
<mysecondcomponent>
/<mycontainer>

我們可以混合使用字符串與 JSX,這也是 JSX 很類似於 HTML 的地方:


Here is a list:

  • Item 1

  • Item 2



某個 React 組件不可以返回多個 React 元素,不過單個 JSX 表達式是允許包含多個子元素的;因此如果我們希望某個組件返回多個並列的子元素,就需要將它們包裹在某個 div 中。

JavaScript 表達式

我們可以傳入包裹在 {} 內的任意 JavaScript 表達式作為子元素,譬如下述聲明方式渲染的結果是相同的:

<mycomponent>foo/<mycomponent>
<mycomponent>{'foo'}/<mycomponent>

這種模式常用於渲染 HTML 列表:

function Item(props) {
return
  • {props.message}
  • ;
    }

    function TodoList() {
    const todos = ['finish doc', 'submit pr', 'nag dan to review'];
    return (

      {todos.map((message) => <item>)}

    );
    }

    JavaScript 函數

    正常情況下 JSX 中包含的 JavaScript 表達式會被解析為字符串、React 元素或者列表;不過 props.children 是允許我們傳入任意值的,譬如我們可以傳入某個函數並且在自定義組件中調用:

    // 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
    {items}
    ;
    }
    function ListOfTenThings() {
    return (
    <repeat>
    {(index) =>
    This is item {index} in the list
    }
    /<repeat>
    );
    }

    布爾值與空值

    false,null,undefined 與 true 是有效的子元素,不過它們並不會被渲染,而是直接被忽略,如下的 JSX 表達式會被渲染為相同結果:



    {false}

    {null}

    {undefined}

    {true}


    避免 XSS 注入攻擊

    最後需要提及的是,React 中 JSX 能夠幫我們自動防護部分 XSS 攻擊,譬如我們常見的需要將用戶輸入的內容再呈現出來:

    const title = response.potentiallyMaliciousInput;
    // This is safe:
    const element =

    {title}

    ;

    在標準的 HTML 中,如果我們不對用戶輸入作任何的過濾,那麼當用戶輸入


    分享到:


    相關文章: