React專題:JSX

本文是『horseshoe·React專題』系列文章之一,後續會有更多專題推出
來個人 GitHub repo 閱讀完整的專題文章
來個人 我的博客 得到無與倫比的閱讀體驗

話說PHP是世界上最好的語言(笑)。javascript

由於它的入門門檻極低。php

<?php
$str = '<ul>';
foreach ($fruits as $fruit) {
    $str += '<li>' . $fruit . '</li>';
}
$str += '</ul>';
?>

不少年前,這種字符串拼接開發網頁的方式很是流行。html

可是這種寫法有兩個問題:前端

  • 容易形成XSS注入,有極大的安全風險。
  • 拼接的寫法很繁瑣。

因而facebook的工程師開始動歪腦筋了。vue

XHP

他們的解決方案也很新穎,就是在代碼裏直接寫標籤,而不是將標籤視爲字符串。java

前面說到,字符串拼接很容易形成XSS注入。那麼什麼是XSS注入呢?react

好比惡意用戶輸入這麼一段內容:<script>code</script>,就可能被程序識別爲一段腳本,他能夠在腳本里面幹任何事情。git

因而人們想到的辦法是對全部輸入轉義,轉義的做用就是讓全部標籤沒法被識別爲標籤,而只是標籤寫法的字符串。用戶的輸入就會原本來本的展現在頁面上。github

可是輸入轉義也有問題,就是容易把字符串拼接的標籤也給轉義了。你們應該看過頁面上大段大段的標籤寫法的文本吧。數組

咱們來看看XHP的寫法。

<?hh
$post =
    <div class="post">
        <h2>{$post}</h2>
        <p><span>Hey there.</span></p>
        <a href={$like_link}>Like</a>
    </div>;

誒,是否是有點眼熟?

XHP把標籤與字符串區別開來了,變成腳本語法的一部分。

這正好解決了前面提到的兩個問題:

  • 標籤就是標籤,字符串就是字符串,再也別想渾水摸魚。
  • 像寫腳本同樣寫標籤,是否是爽多了?

JSX

其實facebook一直在前端組件化方面作各類嘗試,但都不是特別成功。

直到2013年,工程師Jordan Walke提出一個大膽的想法:把XHP的寫法遷移到JavaScript中來。即使有XHP的案例在前,你們仍是以爲這個想法很瘋狂。

不過,facebook極爲優秀的工程師文化最終促成了這種嘗試。這一嘗試不得了,開了天眼。

自此以後就開啓了React的開掛之路。

const element = <h1>Hello React!</h1>;

這不就是facebook一直在苦苦求索的前端組件化方案嗎?

刀耕火種時期的前端,入口是HTML,腳本和樣式被引入到HTML頁面上。這是一種分離化的思想,以語言爲最小顆粒。

然而通過大量痛苦的實踐,人們發現之內容爲最小顆粒纔是正解。以組件爲單位,頁面結構、樣式和功能都被集成在組件內部,對開發者來講組件就是一個黑匣子,只能經過暴露出來的接口使用組件。這是一種封裝的思想,目的固然是爲了複用。

固然,目前React還沒法實現真正意義上的CSS封裝,不過以當下前端的關注度,CSS被完全招安也指日可待。

語法

標籤的寫法和HTML同樣,只不過融入到了JavaScript中。

組件,其實就是自定義標籤,首字母必須大寫,爲了與原生標籤區別開來。

若是標籤或組件沒有包含內容,能夠採用自閉合標籤寫法。

const element = <App />;

JSX會自動忽略falsenullundefined

標籤的class屬性和for屬性要用className屬性和htmlFor屬性代替。

組件返回多個標籤或多個組件必需要用一個標籤或組件包裹,也就是說只能有一個頂層元素。

可是,React16以上的版本支持用空標籤包裹或者直接返回數組。這樣的好處就是沒必要添加不少無用的標籤使頁面變得更加臃腫。

import React, { Fragment } from 'react';

const App = () => {
    return (
        <Fragment>
            <div>React</div>
            <div>Vue</div>
            <div>Angular</div>
        </Fragment>
    );
}

export default App;
import React from 'react';

const App = () => {
    return (
        <>
            <div>React</div>
            <div>Vue</div>
            <div>Angular</div>
        </>
    );
}

export default App;
import React from 'react';

const App = () => {
    return [
        <div key="1">React</div>,
        <div key="2">Vue</div>,
        <div key="3">Angular</div>,
    ];
}

export default App;

表達式

標籤裏確定要寫一些變量,要否則頁面就是死的。

怎麼寫變量呢?用花括號包圍。

const name = 'React';
const element = <h1>Hello {name}!</h1>;

若是我想插入一個對象字面量怎麼辦?

很簡單,再包裹一層花括號。

const obj = { name: 'React' };
const element = <h1 style={{ color: '#f66' }}>Hello {name}!</h1>;

實際上花括號語法支持全部的表達式。

那麼問題來了,什麼是表達式?

簡單來說,表達式的主要做用是計算和聲明,老是有返回值。與之相對,語句的主要做用是邏輯和動做,沒有返回值。

如下表達式JSX都支持。

const a = <button onClick={() => console.log('react')}>click</button>;

const b = <button onClick={function (){ console.log('react') }}>click</button>;

const c = <div>{popular ? 'react' : 'vue'}</div>;

const d = <div>{popular && 'react'}</div>;

const e = <div>{renderSomething()}</div>;

像賦值語句、判斷語句和循環語句JSX都不支持。

那開發者要渲染一個列表怎麼辦?

for循環語句確定是不行的,好在咱們有map函數。由於從上例咱們知道,JSX是支持函數執行表達式的。

forEach函數行不行呢?不行,由於它沒有返回值。也就是說,filter、find、reduce等有返回值的遍歷函數都是能夠的。

import React, { Component } from 'react';

const list = ['react', 'vue', 'angular'];

class App extends Component {
    render() {
        return (
            <div>{list.map(value => <div key={value}>{value}</div>)}</div>
        );
    }
}

export default App;

編譯

不知道大家有沒有這樣的疑問:

  • 爲何返回多個標籤或組件必需要用一個標籤或組件包裹?
  • 爲何在根本沒有使用React這個變量的狀況下還要import React

這裏就要講到JSX的編譯。

由於JSX不是正確的JavaScript語法,它要通過編譯才能被瀏覽器識別。

目前JSX的編譯工做是由babel來完成的。

咱們來看看編譯都作了哪些工做。

下面的例子,後者是前者編譯後的結果。

const app = (
    <div className="form">
        <input type="text" />
        <button>click</button>
    </div>
);
const app = React.createElement(
    "div",
    { className: "form" },
    React.createElement("input", { type: "text" }),
    React.createElement(
        "button",
        null,
        "click",
    ),
);

能夠看到,標籤最後變成了一個函數執行表達式,第一個參數是標籤名,第二個參數是屬性集合,以後的參數都是子標籤。

看到這裏,相信也不用我解釋了,前面提出的兩個問題恍然大悟。

整個UI其實是經過層層嵌套的React.createElement方法返回的,因此咱們要在文件開頭import React,不然編譯後就會發現createElement沒有定義。

React.createElement執行的結果是一個對象,對象的屬性描述了標籤或組件的性狀,對象再嵌套子對象。若是頂層返回多個標籤,就沒法表達爲一個對象了。

因爲React16引入了Fiber機制,使得返回多標籤成爲可能(並不清楚緣由)。

同時也回答了爲何標籤的class屬性和for屬性要用className屬性和htmlFor屬性代替。在標籤裏屬性怎麼寫都無所謂,可是classfor是JavaScript中的關鍵字,因此要換一種寫法。

React裏面傳遞props有一種寫法,若是傳遞的是一個對象,能夠用擴展運算符很方便的傳遞。

下面的例子,value先是被擴展運算符將屬性分解,而後又被一個對象包裹。這裏只是作了一個淺拷貝,並無其餘的含義。因此最終傳遞給組件的仍然是一個對象。

因此疑問就來了,一般給組件傳遞屬性都是鍵值對的形式,直接傳遞一個對象也能夠嗎?

其實全部的屬性最後都會放到一個對象裏面,因此兩種寫法異曲同工。React只不過給了一種快捷方式。

瞭解編譯的過程,不少寫法都很好理解了。

const value = { a: 1, b: 2 };
const element = <App a={value.a} b={value.b} />;
const value = { a: 1, b: 2 };
const element = <App {...value} />;

React專題一覽

什麼是UI
JSX
可變狀態
不可變屬性
生命週期
組件
事件
操做DOM
抽象UI

相關文章
相關標籤/搜索