讓一隻猴子在打字機上隨機地按鍵,當按鍵時間達到無窮時,幾乎必然能夠打出任何給定的文字,比如莎士比亞的全套著作。

– 《無限猴子定理》- 埃米爾·博雷爾

讓一隻猴子看這篇文章,一直看,一直看,當閱讀時間達到無窮時,幾乎必然能夠理解這篇文章。

– 《真 · 無限猴子定理》- 初雪

其實標題打錯了,是猴子能看懂。
看不懂就別怪我囉,至少可以證明你不是猴子(X

先聲明一下,本文所引用的資源和圖片會儘量以中文為主(因為我英文很爛),有些資源是簡體中文,看不習慣的觀眾可以跳過該部份,還請見諒~

那我們就開始吧~


目錄

  1. React 概述
    1. 下載 React
    2. 什麼是 React?
    3. React 特色 – 為什麼我要用它?
    4. 使用範例
    5. NPM 環境初始建置
    6. Virtual DOM
      1. DOM 是什麼?
      2. 為什麼要用 Virtual DOM?
  2. JSX
    1. 為什麼要用 JSX?
    2. JSX 怎麼寫?
    3. 怎麼編譯 JSX?
    4. Babel 安裝 & 配置
  3. props & states
    1. props
    2. states
  4. Component Lifecycle - 元件的生命周期
  5. 總結
    1. 環境配置懶人包

React 概述

下載 React

首先,到 React 官網 下載 React 最新版
在等待下載的同時來說明一下何為 React。

什麼是 React?

React 是 facebook 開發的一個 JS 函式庫,負責產生與管理前端的 UI 。它並不是框架

React 特色 – 為什麼我要用它?

  1. 用純 JS 在前端產生 HTML (一般來說是在後端產生 HTML 送到前端)
  2. 使用 Virtual DOM,重繪時效率高
  3. 自定義 Component,方便開發
  4. 父子 Component 可透過 props 通訊
  5. 只負責 MVC 的 View 部份,所以不算框架,彈性高
  6. 因為完全是 JS 操作 UI 的關係,使得它可以跟後端分離,達到即時互動、自動更新的效果
  7. 只是一個 JS 函式庫,所以容量小易移植

這樣對 React 有沒有比較明白了呢?

使用範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React!</title>
<script src="build/react.js"></script>
<script src="build/react-dom.js"></script>
<script src="https://npmcdn.com/babel-core@5.8.38/browser.min.js"></script>
</head>
<body>
<div id="example"></div>
<script type="text/babel">
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('example')
);
</script>
</body>
</html>

這是官網的例子,被我仁了
我假設各位都知道 HTML 的語法,這裡不再贅述。
首先,要用 React 的話,必須先引入 React 函式庫,你可以在你剛下載的 React 資料夾中找到。如第 6 行、第 7 行。
第 8 行引入的是 babel 函式庫,用來編譯 JSX 成 JS,至於什麼是 JSX 稍後會解釋。
12 行 ~ 17 行是重點,用 React 在 id 為 example 的元件內渲染出 h1 tag。

NPM 環境初始建置

NPM 原本是 Node.js 套件管理器,至今已經變成前後端的開發利器,我就不多解釋了,想了解的可以請教萬能的 Google 大神
沒 NPM 的可以去 Node.js 官網下載。
首先先建個 package.json
npm init
你執行時所在的目錄就是 NPM 專案的根目錄

填完配置之後裝個 React
npm install --save react react-dom
下載的就是你剛剛去 React 官網下載的東西,只不過使用套件管理系統比較方便開發及使用。所以大家可以把官網下載的刪了

Virtual DOM

DOM 是什麼?

熟悉前端的工程師應該會知道 DOM,如果不知道也沒關係,你總會知道的

但我的手業障重,不想打字 QQ,只好請教萬能的維基大神。

文件物件模型(英語:Document Object Model,縮寫DOM),是W3C組織推薦的處理可延伸標示語言的標準程式介面。

– 仁自 維基

什麼?看不懂?沒關係,我也看不懂。你不是一個人(X
這麼簡陋真的是維基而不是偽基嗎?
看來只好請教開源大溼 mozilla。

文件物件模型(Document Object Model, DOM)是 HTML、XML 和 SVG 文件的程式介面。它提供了一個文件(樹)的結構化表示法,並定義讓程式可以存取並改變文件架構、風格和內容的方法。DOM 提供了文件以擁有屬性與函式的節點與物件組成的結構化表示。節點也可以附加事件處理程序,一旦觸發事件就會執行處理程序。 本質上,它將網頁與腳本或程式語言連結在一起。

– 仁自 MDN

簡單來說 DOM 就是用以定義 HTML、XML 和 SVG 的規範,使得各瀏覽器依此進行實作。這樣一來,標準就統一了,工程師又能繼續爆肝了

為什麼要用 Virtual DOM?

什麼是 Virtual DOM?
從字面上看,是虛擬 DOM。
而實際上,就是虛擬 DOM!
沒錯,就是假的DOM 業障重
假的

首先先來說說 DOM 的問題。
很慢。實在是太慢了,慢到 JS 比他快很多倍。如果要用 JS 在前端即時改變 UI 的話,哪怕只是增加了一點小東西,也要重繪!加三個元件就要重繪三次,如果大量操作元件的話效能很不好。

1
2
3
4
5
6
for (var i = 0; i < 10; i++)
{
var a = document.createElement("a");
a.innerHTML = arr[i];
div.appendChild(a);
}

上面的範例每次 appendChild 時就會重繪一次,總共重繪了 10 次。

為了解決這個問題,React 做了一個 Virtual DOM。
Virtual DOM 是用 JS 打造的虛擬中界層,每當重繪時,就會先在 Virtual DOM 中重繪,再用 diff 演算法比對它跟實際的 DOM 有什麼不同,只要修改不同的地方就好,而且只重繪一次。
因為 JS 速度遠比實際 DOM 快,在 Virtual DOM 中重繪的時間跟 DOM 操作的時間相比可以忽略不計。
關於 diff 演算法和 Virtual DOM 內部結構可以看看這裡

JS 中有個類似的東西叫 DocumentFragment,是一個用 JS 打造的虛擬元件,用以合併處理 DOM,像上面的範例就可以改成:

1
2
3
4
5
6
7
8
9
var frag = document.createDocumentFragment();
for (var i = 0; i < 10; i++)
{
var a = document.createElement("a");
a.innerHTML = arr[i];
frag.appendChild(a);
}
div.appendChild(frag);

這樣就只要重繪一次!
但是這是手動的方式,何況若改變的元件不在同個父元件底下的話還是得重繪多次。
但 Virtual DOM 是全自動,而且沒有以上的限制!

所以 JSX 描述的那些元件都不是實際的 DOM,React 會先在 Virtual DOM 上模擬後再改動實際的 DOM,也就是說,你不可能,也不應該直接操作實體 DOM。


先說說 JSX 吧。若已經會或者不想用 JSX 的話可以先跳到下一段。不過之後的範例會以 JSX 為主。


JSX

為什麼要用 JSX?

前面說過,React 是用純 JS 在前端產生 HTML 的,用原生 JS 寫起來會很麻煩 (一堆 createElement),因此,我們可以使用 JSX,它是 JS / ECMAScript 對 XML 的擴充語法,以 XML-like 的語法表達 JS 產生元件的函數,簡單來說就是語法糖 (Syntatic Sugar)。使用的話有助於精簡程式碼,而且對於大多數前端工程師來說 XML 的格式比較直觀,易於閱讀。

JSX
<a href="https://facebook.github.io/react/">Hello!</a>

等同於

JS
React.createElement('a', {href: 'https://facebook.github.io/react/'}, 'Hello!')

上面的範例中可以發現 JSX 的程式碼明顯精簡易讀。
如果要創建具有父子關係的多個節點,那兩者複雜度就會差更多了。

總結
JSX 是開發 React 推薦使用的語言,你可以不用 JSX,然而那將使程式碼難以閱讀和維護。

JSX 怎麼寫?

HTML 大部份的寫法在 JSX 都可以通用,除了以下幾點限制:

  1. HTML 的 class 屬性在 JSX 須寫為 className (class 為 JSX 保留字)
  2. HTML 的 for 屬性在 JSX 須寫為 htmlFor (for 為 JSX 保留字)
  3. 所有 tag 都須被閉合 (XML 的特性)
    EX:HTML <br> => JSX <br />
  4. 同 JS,註解可以用 /* *///,在 tag 中使用的話則須用大括號 {} 包住
  5. 事件觸發是採用駝峰式命名法而不是全部小寫。
    EX:

    1
    <button onClick={this.c8763}></button>
  6. style 屬性要以 JS 物件的格式設定 (JSON),採用駝峰式命名法而非-,數值的單位是 px,其他單位要用單引號包住 (EX: ‘50%’)。別忘記外面要再加上一層大括號。
    EX:

    1
    <a style={{ fontSize: '16px', color: '#FF0' }}>87</a>

JSX 除了 HTML 外還可以用其他的寫法,例:

  1. 因為 JSX 只是 JS 的擴充語法,JS 語法仍可使用,在 tag 中使用的話則須用大括號 {} 包住
  2. 可以用 class 自定義 Component 來使用,第一個字母必須是大寫 (XML 的特性 + JS 的類別)

另外,JSX 是將 tag 轉換成函數,因此一個頂端 tag 對應一個函數。在 retuen 時只能回傳一個頂端 tag,可以在最外層套一個 div 或 Component 解決這個問題。

以上講的很簡略,如果想知道更多資訊,可以看一下這裡

怎麼編譯 JSX?

JSX 可以像上面的例子一樣被包在 HTML 的 <script type="text/babel"></script> 中,引入 JSX 編譯器 babel,直接在 Client 端解析,但我們一般會將 JSX 分成獨立檔案以便維護以及增加載入效率,然後用 babel 編譯成 JS (現在幾乎都會使用 CommonJS bundler)。
EX:<script type="text/jsx" src="main.jsx"></script>

Babel 安裝 & 配置

npm install --save-dev babel-cli
安裝完就可以用指令 babel 來編譯檔案了。
EX:babel uncompile.js -o compiled.js
意思就是說將 uncompile.js 編譯成 compiled.js。
但要是你真的輸入這個指令,你會發現 compiled.js 的內容一模一樣,顯然沒有進行編譯。
沒編譯是因為你尚未和 Babel 說你到底要編譯什麼。
那麼,要怎麼告訴它呢?

首先,在 NPM 專案的根目錄建立一個名叫 .babelrc 的檔案,並輸入以下內容:

1
2
3
{
"presets": []
}

ECMAScript 是所有瀏覽器實作 JS 所參考的標準模型,其第六版簡稱為 ES6 / ES2015,因為篇幅有限,在下一篇才會講解 ES6 和 ES5 的寫法差異。
總之,使用 ES6 對於開發上會比較方便,但不是所有瀏覽器都支援 ES6 語法,因此我們要把 ES6 轉成 ES5 。
npm install --save-dev babel-preset-es2015

然後 .babelrc 改一下設定:

1
2
3
4
5
{
"presets": [
"es2015"
]
}

這樣 Babel 就可以編譯 ES6 了!但還是不能編譯 JSX。
如果我們想編譯 JSX 的話:
npm install --save-dev babel-preset-react

然後 .babelrc 改一下設定:

1
2
3
4
5
6
{
"presets": [
"es2015",
"react"
]
}

這樣就可以編譯使用 ES6 語法的 JSX 了。

但每次 debug 都要按 F12 看 console,對於我這種懶人來說太麻煩了,於是我就找到了 babel-preset-react-hmre
每當 babel 編譯出錯時都會跑出很狂、很潮、很炫炮的紅底白字喔 ~
npm install --save-dev babel-preset-react-hmre

同樣的 .babelrc 改一下設定:

1
2
3
4
5
6
7
8
9
10
{
"presets": [
"es2015",
"react"
],
"env": {
"development": {
"presets": ["react-hmre"]
}
}

PS. env.development.presets 是代表說開發的時候才會用到的,正式放到 server 上時不會使用。

如果對 Babel 還有興趣可看這裡
但是這樣的話要手動一個一個編譯,像我這種懶人只好用一些 bundle 工具了。
於是我找到了 Webpack

PS. 關於 Webpack 我會在第三篇詳細介紹。


props & states

前面有稍微提到過 props。
沒錯,只有提到一次而已,而且我講的很含糊。等等,別打我,我接下來就要講了啦~

props

props 是 React 父子元件間溝通的橋樑。靜態(唯讀)。
父元件用屬性賦值的方式傳給子元件,子元件用 this.props 讀取。但不應於子元件內變動 (唯讀)。
父元件傳入的 props 改變將造成子元件重繪。

EX:
父元件內

1
2
3
...
<Fuck fuck="?"/>
...

子元件Fuck

1
2
3
...
<a>{this.props.fuck}</a>
...

因為父元件傳入?,所以這裡會顯示?

states

states 是元件內部狀態。動態(可用 setState 改值)。
與一般變數不同的是,它無法直接修改(初始化例外),只能用 this.setState() 修改。
每次使用 this.setState() 修改 state 都會造成元件重繪。

EX:
state 初始化

1
2
3
this.state = {
users: []
};

setState

1
2
3
this.setState({
users: list
});

state 初始化

1
<UserList users={this.state.users}/>

這兩個都是很常用到的東西。不管黑貓白貓,能讓頁面重繪的就是好貓
能讓頁面重繪的其實還有一個函數:this.forceUpdate(),強制重繪,不過一般是 debug 才會用到,不推薦在其他情況使用。


Component Lifecycle - 元件的生命周期

這裡比較複雜,先來張圖片。
圖片就仁一下 IBM 的:

先看看右下角的三個生命周期說明。

  • Mounting: 元件初始化
  • Updating: 元件更新
  • Unmounting: 元件卸載

Mounting 跟 Updating 都會觸發元件內的函數 render()
componentWill 和 componentDid 分別是 render 前和後會觸發的函數。
Unmounting 不會觸發 render,也沒有 componentDid 函數,但有 componentWill 函數。

如果要細講的話,可以分成五種情況詳細討論:

  • Mounting:元件初始化 (React.createClass)

    1. getDefaultProps()

      • React.createClass時觸發(早於 constructor)
      • 只會在 Mounting 時呼叫一次
      • 不能使用 setState() (尚未初始化)
      • 回傳的值將設為 props 的預設值 (若回傳物件,是傳址)
    2. getInitialState()

      • React.createElement時觸發 (渲染前觸發)
      • 只會在 Mounting 時呼叫一次
      • 不能使用 setState() (尚未初始化)
      • 回傳的值將設為 state 的初始值 (回傳物件或null
    3. componentWillMount()

      • 只會在 Mounting 時呼叫一次
      • 使用 setState 不會再觸發一次 render,會立即更新
    4. render()

      • 不能使用 setState() (無限遞迴)
      • 一定要改寫,必須且只能回傳一個頂層 JSX Component 或是 null/false (不渲染)
      • 結束後會造成子元件的 Mounting,意即,子元件的生命周期由此開始
    5. componentDidMount()

      • 只會在 Mounting 時呼叫一次
      • 子元件 Mounting 完後才觸發
      • 適合存取 DOM
  • Updating:元件接收的 props 改變時

    1. componentWillReceiveProps(nextProps)

      • 使用 setState 不會再觸發一次 render,會立即更新
      • 若該元件有傳入的 props,則父元件 Updating-render 時會觸發(因為會重傳 props)
      • 若沒有 props,此函數將不會被觸發
    2. shouldComponentUpdate(nextProps, nextState)

      • 不能使用 setState() (無限遞迴)
      • 回傳false的話這次更新直接結束
      • 優化效能用的
    3. componentWillUpdate(nextProps, nextState)

      • 不能使用 setState() (無限遞迴)
    4. render()

      • 詳見 Mounting - 4.
      • 結束後會造成子元件的 Mounting、Updating、Unmounting
    5. componentDidUpdate(prevProps, prevState)

      • 子元件 Mounting、Updating、Unmounting 完後才觸發
  • Updating:元件內部的 state 改變時 (setState) (在這裡任何地方使用 setState 都會造成無限遞迴,除了 componentDidUpdate)
    同 Update-props 之 2 ~ 5,不再贅述

    1. shouldComponentUpdate(nextProps, nextState)
    2. componentWillUpdate(nextProps, nextState)
    3. render()
    4. componentDidUpdate(prevProps, prevState)
  • Updating:使用 forceUpdate 時
    同 Update-props 之 3 ~ 5,不再贅述

    1. componentWillUpdate(nextProps, nextState)
    2. render()
    3. componentDidUpdate(prevProps, prevState)
  • Unmount:元件卸載時

    1. componentWillUnmount()

上面的資料太多,可能一時間難以消化,沒關係,目前記住 render() 就夠了。
下面我再做一些整理:

  • 使用 setState 不會再觸發一次 render,會立即更新

    1. componentWillMount()
    2. componentWillReceiveProps()
  • 有回傳值

    1. getDefaultProps():props 的預設值
    2. getInitialState():state 的初始值
    3. render():一個頂層 JSX Component 或是 null/false (不渲染)
    4. shouldComponentUpdate():true/falsefalse則這次更新直接結束

總結

環境配置懶人包

  • npm init NPM 專案初始化
  • npm install --save react react-dom 安裝 React
  • npm install --save-dev babel-cli babel-preset-es2015 babel-preset-react babel-preset-react-hmre babel - cli & ES6 & JSX & babel-preset-react-hmre

先講到這,這篇講的以概念居多,下篇 就會講 React 寫法了。如果有興趣可以繼續收看~

這篇會不定期更新,如果有打錯或有任何疑問都歡迎留言告知喔~
我們下篇再見~