React 初窺:JSX 詳解 從屬於筆者的 React 與前端工程化實踐系列文章,本文引用借鑑的以及更多 React 相關資料參考 React 學習與實踐資料索引。html
咱們在上文中已經不少次的說起了 JSX,你們也對於基本的基於 JSX 編寫 React 組件全部瞭解。實際上在 JSX 推出之初飽受非議,不少人以爲其很怪異。的確雖然與正統的 HTML 相比其都是類 XML語法的聲明式標籤語言,可是其對於類名強制使用 className、強制要求標籤閉合等特色會讓很多的傳統前端開發者不太適應。JSX 的引入對筆者以前的工做流的衝擊在於不可以直接使用 UI 部門提供的頁面模板,而且由於組件化的分割與預編譯,UI 比較麻煩地直接在瀏覽器開發工具中調整CSS樣式而後保存到源代碼中。JSX 本質上仍是屬於 JavaScript,這就避免了咱們重複地學習不一樣框架或庫中的指令約定,而能夠直接使用 JavaScript 來描述模板渲染邏輯;而在前端框架的工做流中,每每將 JSX 的轉化工做交託於 Babel 等轉化工具,咱們能夠經過以下方式指定 JSX 使用的構建函數:前端
/** @jsx h */
JSX 語言的名字最先出如今遊戲廠商 DeNA,不過其偏重於加入加強語法使得JavaScript 變得更快、更安全、更簡單。而 React 則是依賴於 ECMAScript 語法自己,並無添加擴充語義。React 引入 JSX 主要是爲了方便 View 層組件化,承載了構建 HTML 結構化頁面的職責。這一點與其餘不少的 JavaScript 模板語言殊途同歸,不過React 將 JSX 映射爲虛擬元素,而且經過建立與更新虛擬元素來管理整個 Virtual DOM 系統。譬如咱們 JSX 語法聲明某個虛擬組件時,會被轉化爲React.createElement(component,props,...children)
函數調用,譬如咱們定義了某個MyButton
:react
// 必需要在 JSX 聲明文件中引入 React import React from 'react'; <MyButton color="blue" shadowSize={2}> Click Me </MyButton>
會被編譯爲:前端工程化
React.createElement( MyButton, {color: 'blue', shadowSize: 2}, 'Click Me' )
而若是咱們直接聲明某個DOM元素,一樣會轉化爲createElement函數調用:api
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( <div> <input type="text" value={defaultValue} /> <button onclick="alert('clicked!');">Click Me!</button> <ul> {['un', 'deux', 'trois'].map(function(number) { return <li>{number}</li>; })} </ul> </div> );
這裏咱們還想討論另外一個問題,爲何須要引入 JSX。在 ECAMScript 6 的 ECMA-262 標準中引入了所謂的模板字符串(Template Literals),便可以在 ECMAScript 中使用內嵌的 DSL 來引入 JavaScript 變量,不過雖然模板字符串對於較長的嵌入式 DSL 做用極佳,可是對於須要引入大量做用域中的 ECMAScript 表達式會形成大量的噪音反作用,譬如若是咱們要聲明某個評論框佈局,使用 JSX 的方式以下:瀏覽器
// JSX var box = <Box> { shouldShowAnswer(user) ? <Answer value={false}>no</Answer> : <Box.Comment> Text Content </Box.Comment> } </Box>;
而使用模板字符串的方式以下:安全
// Template Literals var box = jsx` <${Box}> ${ shouldShowAnswer(user) ? jsx`<${Answer} value=${false}>no</${Answer}>` : jsx` <${Box.Comment}> Text Content </${Box.Comment}> ` } </${Box}> `;
其主要缺陷在於由於存在變量的嵌套,須要在做用域中進進出出,很容易形成語法錯誤,所以仍是 JSX 語法爲佳。前端框架
JSX 的官方定義是類 XML 語法的 ECMAscript 擴展,完美地利用了 JavaScript 自帶的語法和特性,並使用你們熟悉的 HTML 語法來建立虛擬元素。JSX 基本語法基本被 XML 囊括了,但也有不少的不一樣之處。React 在定義標籤時,標籤必定要閉合,不然沒法編譯經過。這一點與標準的 HTML 差異很大,HTML 在瀏覽器渲染時會自動進行補全,而強大的 JSX 報錯機制則直接在編譯階段就以報錯的方式指明出來。HTML 中自閉合的標籤(如 <img>
)在 JSX 中也遵循一樣規則,自定義標籤能夠根據是否有子組件或文原本決定閉合方式。另外 DOCTYPE 頭也是一個很是特殊的標誌,通常會在使用 React 做爲服務端渲染時用到。在 HTML 中,DOCTYPE 是沒有閉合的,也就是說咱們沒法直接渲染它。常見的作法是構造一個保存 HTML 的變量,將 DOCTYPE 與整個 HTML 標籤渲染後的結果串聯起來。使用JSX聲明組件時,最外層的組件根元素只容許使用單一根元素。這一點咱們在上文中也陳述過,由於 JSX 語法會被轉化爲 React.createElement(component,props,...children)
調用,而該函數的第一個參數只容許傳入單元素,而不容許傳入多元素。app
註釋
在 HTML 中,咱們會使用 <!-- -->
進行註釋,不過 JSX 中並不支持:
render() { return ( <div> <!-- This doesn't work! --> </div> ) }
咱們須要以 JavaScript 中塊註釋的方式進行註釋:
{/* A JSX comment */} {/* Multi line comment */}
數組
JSX 容許使用任意的變量,所以若是咱們須要使用數組進行循環元素渲染時,直接使用 map、reduce、filter 等方法便可:
function NumberList(props) { const numbers = props.numbers; return ( <ul> {numbers.map((number) => <ListItem key={number.toString()} value={number} /> )} </ul> ); }
條件渲染
在JSX中咱們不能再使用傳統的if/else條件判斷語法,可是可使用更爲簡潔明瞭的Conditional Operator運算符,譬如咱們要進行if操做:
{condition && <span>爲真時進行渲染</span> }
若是要進行非操做:
{condition || <span>爲假時進行渲染</span> }
咱們也可使用常見的三元操做符進行判斷:
{condition ? <span>爲真時進行渲染</span> : <span>爲假時進行渲染</span> }
若是對於較大的代碼塊,建議是進行換行以提高代碼可讀性:
{condition ? ( <span> 爲假時進行渲染 </span> ) : ( <span> 爲假時進行渲染 </span> )}
style 屬性
JSX 中的 style 並無跟 HTML 同樣接收某個 CSS 字符串,而是接收某個使用 camelCase 風格屬性的 JavaScript 對象,這一點卻是和DOM 對象的 style 屬性一致。譬如:
const divStyle = { color: 'blue', backgroundImage: 'url(' + imgUrl + ')', }; function HelloWorldComponent() { return <div style={divStyle}>Hello World!</div>; }
注意,內聯樣式並不能自動添加前綴,這也是筆者不太喜歡使用 CSS-in-JS 這種形式設置樣式的的緣由。爲了支持舊版本瀏覽器,須要提供相關的前綴:
const divStyle = { WebkitTransition: 'all', // note the capital 'W' here msTransition: 'all' // 'ms' is the only lowercase vendor prefix }; function ComponentWithTransition() { return <div style={divStyle}>This should work cross-browser</div>; }
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 時,必需要使用屬性表達式。譬如 <input type='checkbox' checked={true}>
能夠簡寫爲<input type='checkbox' checked>
,而 <input type='checkbox' checked={falsed}>
即不能夠省略 checked 屬性。
自定義屬性
若是在 JSX 中向 DOM 元素中傳入自定義屬性,React 是會自動忽略的:
<div customProperty='a' />
不過若是要使用HTML標準的自定義屬性,即以 data-*
或者 aria-*
形式的屬性是支持的。
<div data-attr='attr' />
JSX 表達式中容許在一對開放標籤或者閉合標籤之間包含內容,這便是所謂的子元素,本部分介紹 JSX 支持的不一樣類別的子元素使用方式。
字符串
咱們能夠將字符串放置在一對開放與閉合的標籤之間,此時所謂的 props.children
即就是字符串類型;譬如:
<MyComponent>Hello World!</MyComponent>
就是合法的 JSX 聲明,此時 MyComponent
中的 props.children
值就是字符串 Hello World!
;另外須要注意的是,JSX 會自動移除行首與行末的空格,而且移除空行,所以下面的三種聲明方式渲染的結果是一致的:
<div>Hello World</div> <div> Hello World </div> <div> Hello World </div> <div> Hello World </div>
JSX 嵌套
咱們能夠嵌套地使用 JSX,即將某些 JSX 元素做爲子元素,從而容許咱們方便地展現嵌套組件:
<MyContainer> <MyFirstComponent /> <MySecondComponent /> </MyContainer>
咱們能夠混合使用字符串與 JSX,這也是 JSX 很相似於 HTML 的地方:
<div> Here is a list: <ul> <li>Item 1</li> <li>Item 2</li> </ul> </div>
某個 React 組件不能夠返回多個 React 元素,不過單個 JSX 表達式是容許包含多個子元素的;所以若是咱們但願某個組件返回多個並列的子元素,就須要將它們包裹在某個 div
中。
JavaScript 表達式
咱們能夠傳入包裹在 {}
內的任意 JavaScript 表達式做爲子元素,譬以下述聲明方式渲染的結果是相同的:
<MyComponent>foo</MyComponent> <MyComponent>{'foo'}</MyComponent>
這種模式經常使用於渲染 HTML 列表:
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> ); }
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 <div>{items}</div>; } function ListOfTenThings() { return ( <Repeat numTimes={10}> {(index) => <div key={index}>This is item {index} in the list</div>} </Repeat> ); }
布爾值與空值false
,null
,undefined
與 true
是有效的子元素,不過它們並不會被渲染,而是直接被忽略,以下的 JSX 表達式會被渲染爲相同結果:
<div /> <div></div> <div>{false}</div> <div>{null}</div> <div>{undefined}</div> <div>{true}</div>
最後須要說起的是,React 中 JSX 可以幫咱們自動防禦部分 XSS 攻擊,譬如咱們常見的須要將用戶輸入的內容再呈現出來:
const title = response.potentiallyMaliciousInput; // This is safe: const element = <h1>{title}</h1>;
在標準的 HTML 中,若是咱們不對用戶輸入做任何的過濾,那麼當用戶輸入 <script>alert(1)<script/>
這樣的可執行代碼以後,就存在被 XSS 攻擊的危險。而 React 在實際渲染以前會幫咱們自動過濾掉嵌入在 JSX 中的危險代碼,將全部的輸入進行編碼,保證其爲純字符串以後再進行渲染。不過這種安全過濾有時候也會對咱們形成不便,譬如若是咱們須要使用 ©
這樣的實體字符時,React 會自動將其轉移最後致使沒法正確渲染,咱們能夠尋找以下幾種解決方法:
直接使用 UTF-8 字符或者使用對應字符的 Unicode 編碼
使用數組封裝
直接插入原始的 HTML,React 爲咱們提供了 dangerouslySetInnerHTML 屬性,其相似於 DOM 的 innerHTML 屬性,容許咱們聲明強制直接插入 HTML 代碼:
function createMarkup() { return {__html: 'First · Second'}; } function MyComponent() { return <div dangerouslySetInnerHTML={createMarkup()} />; }