React 初窺:JSX 詳解

React 初窺:JSX 詳解 從屬於筆者的 React 與前端工程化實踐系列文章,本文引用借鑑的以及更多 React 相關資料參考 React 學習與實踐資料索引html

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) 函數調用,譬如咱們定義了某個MyButtonreact

// 必需要在 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 轉換器以外,咱們還可使用 JSXDOMMercury 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 語法

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>
  );
}
  • 布爾值與空值
    falsenullundefinedtrue 是有效的子元素,不過它們並不會被渲染,而是直接被忽略,以下的 JSX 表達式會被渲染爲相同結果:

<div />

<div></div>

<div>{false}</div>

<div>{null}</div>

<div>{undefined}</div>

<div>{true}</div>

避免 XSS 注入攻擊

最後須要說起的是,React 中 JSX 可以幫咱們自動防禦部分 XSS 攻擊,譬如咱們常見的須要將用戶輸入的內容再呈現出來:

const title = response.potentiallyMaliciousInput;
// This is safe:
const element = <h1>{title}</h1>;

在標準的 HTML 中,若是咱們不對用戶輸入做任何的過濾,那麼當用戶輸入 <script>alert(1)<script/> 這樣的可執行代碼以後,就存在被 XSS 攻擊的危險。而 React 在實際渲染以前會幫咱們自動過濾掉嵌入在 JSX 中的危險代碼,將全部的輸入進行編碼,保證其爲純字符串以後再進行渲染。不過這種安全過濾有時候也會對咱們形成不便,譬如若是咱們須要使用 &copy; 這樣的實體字符時,React 會自動將其轉移最後致使沒法正確渲染,咱們能夠尋找以下幾種解決方法:

  • 直接使用 UTF-8 字符或者使用對應字符的 Unicode 編碼

  • 使用數組封裝

  • 直接插入原始的 HTML,React 爲咱們提供了 dangerouslySetInnerHTML 屬性,其相似於 DOM 的 innerHTML 屬性,容許咱們聲明強制直接插入 HTML 代碼:

function createMarkup() {
  return {__html: 'First &middot; Second'};
}

function MyComponent() {
  return <div dangerouslySetInnerHTML={createMarkup()} />;
}
相關文章
相關標籤/搜索