React 介紹及實踐教程

發表於2015-10-09

React 是由 Facebook 推出的一個 JavaScript 框架,主要用於前端開發。本文主要介紹 React 的一些重要的基本概念,包括虛擬 DOM 和元件。於此同時還會涉及 React 所使用的 JSX 和 Flux 單向資料流的介紹說明,並通過一個完整的例項讓您可以快速地瞭解 React。

引言

React 是近期非常熱門的一個前端開發框架,其本身作為 MVC 中的 View 層可以用來構建 UI,也可以以外掛的形式應用到 Web 應用非 UI 部分的構建中,輕鬆實現與其他 JS 框架的整合,比如 AngularJS。同時,React 通過對虛擬 DOM 中的微操作來實對現實際 DOM 的區域性更新,提高效能。其元件的模組化開發提高了程式碼的可維護性。單向資料流的特點,讓每個模組根據資料量自動更新,讓開發者可以只專注於資料部分,改善程式的可預測性。

React 概況介紹

React 中最基礎最重要的就是 Component 了,它的構造和功能相當於 AngularJS 裡面的 Directive,或是其他 JS 框架裡面的 Widgets 或 Modules。Component 可以認為是由 HTML、CSS、JS 和一些內部資料組合而成的模組。當然 Component 也可以由很多 Component 組建而成。不同的 Component 既可以用純 JavaScript 定義,也可以用特有的 JavaScript 語法 JSX 建立而成。關於 JSX,我們會在後面加以詳細介紹。為了能讓您更好的理解 Component 概念,讓我們來看一下本文的例項。

圖 1. 例項展示

本文的例項是一個顏色盤,當使用者把滑鼠移動到底部的顏色條上時,上方方框內會提示對應的顏色。如果用 React 來實現這個例項,這個顏色盤會被分割成為多個 Component。最外面一層父級 Component 稱為 ColorPanel,同時它包含了兩個子 Component:上方的 ColorDisplay 和下方的 ColorBar。我們在 ColorPanel 的屬性裡面賦值了該顏色盤所要顯示的所有顏色,ColorBar 獲取這個顏色屬性渲染出所有的顏色,當滑鼠移動到 ColorBar 的某個顏色上時,ColorDisplay 顯示對應的顏色和文字。在這個過程中,我們並不需要去手動觸發所有元件的渲染,只需要在程式開始時渲染 ColorPanel。當 ColorPanel 的當前選中顏色這一狀態發生變化時,子 Component(比如 ColorDisplay)就會根據這一狀態自動重新渲染。這正是 React 的奇妙所在——使用者更多的去關心資料的變化,而無需在意 DOM 節點的渲染。

建立第一個 Component

通過上面上面一段的介紹,相信您已經對於 React 有了一定的概念,下面就讓我們開始著手自己開發一個 React 的小例項吧!首先第一步就是建立一個簡單的 Component。

清單 1. 建立 ColorPanel Component

清單 1 為建立 ColorPanel 這個 Component 的程式碼。通過 createClass 方法來建立了一個 Component。其中,createClass 需要傳入一個 object 物件,這個物件可以定義不同的屬性,比如 getInitialState、getDefaultProps、render 等等。 此處值得一提的是,與其它屬性不同,render 方法是必須存在的,因為 render 方法的返回值代表的是 Component 的 template。 清單 1 的例子即是建立一個包含著 Color Panel 字樣的 Component。

當然,createClass 的功能是用來建立 Component 物件,如果要渲染這個 Component 到顯示節點上,就需要呼叫 React.render 方法。在這個方法中包含兩個引數,第一個引數需要指定需要渲染的 Component 物件,第二個參數列示該 Component 所掛載到父節點。

看到這裡,您可能會覺得疑惑:為什麼 JavaScript 程式碼中嵌入了 HTML 標籤?其實這段巢狀在在 render 方法裡面的並非真正意義上的 HTML,React 稱之為“JSX”。JSX 可以允許把 HTML 類似的語法轉換為輕量級的 JavaScript 物件。

清單 2. 把 JSX 轉化成 JavaScript 的方式來建立 Component

在清單 2 的程式碼為 JSX 轉化為 JavaScript 的表達方式。然而,在實際使用 React 開發中, 選著使用 JSX 的語法方式可以使程式碼結構更加清晰簡潔。使用 JSX,開發人員還可以跳出 HTML 節點的限制自定義 Component。

開發人員只需要在 HTML 頁面中簡單的新增清單 3 中的 JS 檔案——React 提供的解析轉化指令碼,就可以輕鬆實現 JSX 到 JavaScript 的轉化。

清單 3. 在 HTML 中引用 JSX 轉化指令碼

介紹完 JSX,此處需要引入另一個重要概念——虛擬 DOM。React 引入這種概念是因為虛擬 DOM 把當前真實的 DOM 節點轉化成 JavaScript 物件。這使得 React 可以追蹤當前虛擬 DOM(某些資料變化後生成)和之前的虛擬 DOM(某些資料變化之前)的差異。React 通過兩者對比提取出來這些差異點,然後再在真實的 DOM 節點上執行必要的更新。在傳統的開發中,通常 UI 上諸多狀態變化會讓維護應用的狀態變得很困難和複雜。React 通過檢測狀態變化來每次重新渲染虛擬 DOM 節點,然後按需自動更新真實節點,這種方式可以讓開發人員可以簡單地專注在應用的狀態上。

總結一下上述操作的過程是: 首先某一訊號觸發應用中某些資料發生改變,React 就會重新渲染整個虛擬 DOM。然後 React 會比較現在的虛擬 DOM 與之前的虛擬 DOM 的差異,獲知哪些是需要更新的,最後在真實的 DOM 上應用這些必要的更新。更具體的虛擬 DOM 技術,會在下面章節中詳細介紹。

給 Component 新增 state

上文中我們提到 UI 介面元素的諸多狀態使得維護 UI 頁面變的十分困難, React 卻讓著一切變的輕鬆起來。因為在 React 中,每一個 Component 都會維護自己的 state,當子 Component 需要這些 state 作為 props 時,則將其順序傳遞下去。換句話說,如果有多個層級的 Component,它們公共的父級 Component 負責管理 state,然後通過 props 把 state 傳遞到子 Component 中。

清單 4. 為 Component 新增 state

清單 4 是一個 Component 使用內部 state 的一個例子。您會注意在這裡建立元件時新增了一個 getInitialState 方法,它是用來設定 Component 的 state 的,它返回的是一個物件包含了 Component 的 data 或者 state 值。在這個例子中,也是通過這個方法告訴 Component 需要儲存一個叫 selectedColor 的物件,在 Component 的 render 方法中就可以通過{this.state.selectedColor}來使用了。

當然,Component 也可以來更改這個內部狀態值,這要通過 setState 方法。之前說的“某一訊號觸發應用中某些資料發生改變”指的就是 setState 方法。不管 setState 方法何時呼叫,虛擬 DOM 都會被重新渲染,之後執行差異演算法並按需更新真實的 DOM。

從父 Component 中獲取 State

在前面的介紹也提到了不可或缺的 props,它指的就是從父 Component 傳遞到子 Component 的資料。通過 props,React 框架可以保持良好的資料的直線傳遞——在最頂層的父級 Component 中處理所需要使用的特殊資料,當子的 Component 也需要使用時就把它們通過 props 來傳遞下去。 事實上 props 對於 Component 就像 Attribute 對於 HTML 一樣,當我們提供了 property 的 name 和 value 時就傳遞到 Component 裡面了。在 Component 裡面通過 this.props 來獲取 Properties。清單 5 中展示了從父級節點獲取 state 的例項。

清單 5. 從父節點中獲取 state

我們可以通過改變 render 函式的內容來改變元件的行為。但是如果想要根據外部的資訊來改變元件的行為,就需要使用 Properties。當然,我們也可以通過 getDefaultProps()來返回 property 的初始值,該方法的返回值必須是一個物件。任何通過 getDefaultProps()返回的物件都會被所用例項共享。清單 6 中展示了通過 getDefaultProps 方法獲取 props 初始值的例項。

清單 6. 通過 getDefaultProps 獲取 props 初始值

Component 的生命週期

每個 Component 都有自己的生命週期,在此期間 React 提供了很多方法用於對不同階段的元件加以操作。下圖 4 介紹了一個 Component 在其生命中期中可以執行的方法。

圖 2. Component 的生命週期

元件的生命週期主要可以分為三個階段,Mounting、Updating、Unmounting。React 在每一階段開始前提供了 will 方法用於執行恰好在這一階段開始前需要實行的操作,為每一段結束之後提供了 did 方法用於執行恰好這一階段結束時需要實現的操作。下面我們詳細說明一下每一個階段的具體實現。

首先在 Mounting 階段,Component 通過 React.createClass 被建立出來,然後呼叫 getInitialState 方法初始化 this.state。在 Component 執行 render 方法之前,通過呼叫 componentWillMount(方法修改 state 狀態),然後執行 render。Reder 的過程即是元件生成 HTML 結構的過程。在 render 之後,Component 會呼叫 componentDidMount 方法。在這個方法執行之後,開發人員才能通過 this.getDOMNode()獲取到元件的 DOM 節點。

當 Component 在 mount 結束之後,它當中有任何資料修改導致的更新都會在 Updating 階段執行。Component 的 componentWillReceiveProps 方法會監聽元件中 props。監聽到 props 發生修改,它會比對新的資料與之前的資料之間是否存在差別進而修改 state 的值。當比對的結果為資料變化需要對 Component 對應的 DOM 節點做出修改的時候,shouldCoponentUpdate 方法它會返回 true 用於觸發 componentWillUpdate 和 componentDidUpdate 方法。在預設的情況下 shouldComponentUpdate 返回為 true。有些特殊的情況是當 component 中的 props 發生修改,但是其本身資料並沒有改變,或者是開發人員手工設定 shouldComponentUpdate 為 false 時,React 就不會更新這個 component 對應的 DOM 節點了。與 componentWillMount 和 componentDidMount 相類似,componentWillUpdate 和 componentDidUpdate 也分別在元件更新的 render 過程前後執行。

當開發人員需要將 component 從 DOM 中移除時,就會觸發 UnMounting 階段。在這個階段中,React 只提供了一個 componentWillUnmount 方法在解除安裝和銷燬這個 component 之前觸發,用於刪除 component 中的 DOM 元素等。

虛擬 DOM

在傳統的 Web 應用中,我們往往會把資料的變化實時地更新到使用者介面中,於是每次資料的微小變動都會引起 DOM 樹的重新渲染。如果當前 DOM 結構較為複雜,頻繁的操作很可能會引發效能問題。React 為了解決這個問題,引入了虛擬 DOM 技術。圖 2 為傳統 Web 應用於 React Web 應用的對比圖。

圖 3. 傳統 Web 應用於使用 React 的 Web 應用對比

(引用自:http://www.slideshare.net/mitchbox/learning-react-i )

虛擬 DOM 是一個 JavaScript 的樹形結構,包含了 React 元素和模組。元件的 DOM 結構就是對映到對應的虛擬 DOM 上,React 通過渲染虛擬 DOM 到瀏覽器,使得使用者介面得以顯示。與此同時,React 在虛擬的 DOM 上實現了一個 diff 演算法,當要更新元件的時候,會通過 diff 尋找到要變更的 DOM 節點,再把這個修改更新到瀏覽器實際的 DOM 節點上,所以在 React 中,當頁面發生變化時實際上不是真的渲染整個 DOM 樹。

React 虛擬 DOM 中的諸多如 div 一類的標籤與實際 DOM 中的 div 是相互獨立的兩個概念,它是一個純粹的 JS 資料結構,它只是提供了一個與 DOM 類似的 Tag 和 API。React 會通過自身的邏輯和演算法,轉化為真正的 DOM 節點。也正是因為這樣的結構,虛擬 DOM 的效能要比原生 DOM 快很多。圖 3 模擬了虛擬 DOM 資料更新的場景:

圖 4. 虛擬 DOM 中資料的更新過程

(引用自:http://www.slideshare.net/AllThingsOpen/an-introduction-to-reactjs? )

在當前頁面中資料發生變化時,React 會重新構建其 DOM 樹,也就是我們所說的虛擬 DOM。然後 React 會將這個新構建好的虛擬 DOM 樹與更新之前的虛擬 DOM 樹加以比對得出結構差異:增加一個黃色節點,刪除一個粉紅色節點。通過這樣的對比,使得瀏覽器實際的更新過程中,可以只修改變更部分——黃色節點和粉紅色節點,而其它節點保持不變。

Flux

Flux 其實就是一種單向資料流的模式。與常見的 View 和 Store 相互互動的 MVC 模式不同的是,Flux 引入了 Dispatcher。使用者在 View 上的操作,不會直接引起 Store 的變化,而是通過 Action 觸發在 Dispatcher 上註冊的回撥函式,從而觸發 Store 資料的更新,最終元件會重新渲染。這樣一來,即可以保證資料流是單向流通的。Flux 相對於 MVC,讓事情變得更加可以預見。

圖 5.Flux 的資料流向圖

(引用自:http://reactjs.cn/react/docs/flux-overview.html

當使用者和 View(Component)互動的時候,View 會觸發一個 Action 到中央 Dispatcher。然後將 Action 再分配到儲存著應用的資料和業務邏輯的 Store 裡面,Store 內資料的更新會引起所有 View 的更新。這個對 React 這種申明式的語法非常適用,這樣就允許 Store 在更新的時候,不用去關注不同的 View 和 State 是如何互動的。

Dispatcher 類似於一箇中央樞紐,管理著所有的資料流。它本身沒有什麼業務邏輯,只是負責把不同的 Action 分發到不同的 Store 中。其邏輯是 Store 會在 Dispatcher 中按照不同的 actiontype 註冊不同的回撥函式。當 Action 到達 Dispatcher 的時候,Dispatcher 會根據 Action 的 type 找到之前註冊 Store,並觸發其回撥函式,從而更新 Store 的資料,達到 View 的更新。

Store 儲存著應用的 state 和邏輯。它們的角色類似於傳統 MVC 裡面的 model,但是它們可以管理許多物件的狀態,它們不只是代表了一個 ORM 模型或者說像 Backbone 裡面的 collection。它們更多的是管理著應用的一個特定領域狀態。就像之前提到的,Store 會註冊自己和對應的回撥函式到 Dispatcher 上。回撥函式會接收 Action 作為引數。不同的 actiontype 會對應不同的業務邏輯,從而更新不同的 state。當 Store 被更新後會廣播告知 View 去更新 Component。

Views 和 controller-views:React 提供了可組建的、會自動重新渲染的 View。在巢狀結構的最上層,有一種特殊型別的 View 監聽這著 Store 的廣播,我們稱之為 controller-view,它會獲取 Store 裡面的資料,然後會呼叫 setState 方法,從而觸發該 Component 的 render 方法和子 Component 的 render 方法。

我們通常會把 Store 的所有的 state 放在一個物件裡面沿著鏈式地 View 傳遞下去,這樣允許不同的子孫 Component 都能獲取的它們所需。並且把這種 controller-view 放在巢狀結構的最頂層,是為了保持子孫 Component 邏輯更簡單,而且也可以減少需要管理的 props 的數量。

Dispatcher 暴露了一個方法,允許我們來觸發 Action 到 Store 的轉發,而且可以把 Action 作為引數傳遞過去。Action 除了來自使用者和 View 的互動,也可以來自 server

例項分析

通過對於上一個部分的瞭解,相信測試此時的您心中已經有了 React 的基本輪廓。下面讓我來看一下完整的 ColorPanel 例項的具體實現吧!首先,在 index.html 裡面引入三個 JSX 檔案。這三個檔案就是我們使用 React 實現 ColorPanel 的原始碼。

清單 7. HTML 檔案中引用的 JSX 檔案

清單 8. colorPanel.jsx 程式碼

清單 9. colorBar.jsx 程式碼

清單 10. colorDisplay.jsx 程式碼

這個例項的實現原理是:當使用者把滑鼠停留在某一個 colorbar 上時,就會觸發 mouseover 上繫結的 onColorHover 事件,同時傳遞了當前顏色的 ID。然後,onColorHover 事件根據傳遞進來的顏色 ID 重置 selectedColor。selectedColor 作為 ColorDisplay 這個 Component 的 state 值,當它發生變化時引發的虛擬 DOM 節點的重新 render 進而引起了真實 DOM 節點的重新渲染。於是頁面上所顯示的文字內容和顏色型別轉換成為我們所選擇的對應值。

在實際的程式碼邏輯中,Color Bar 的顏色為六種固定顏色,於是我們把這些顏色的值(value)的定義存放在父級 Component 的 props 中,通過 getDefaultProps 來設定預設的屬性以及初始時所選著的顏色(此處選擇預設 ID 為 1 的顏色)。接下來在 ColorPanel 中設定一個 selectedColor 作為內部狀態值 state 表示當前所選中的顏色。它的定義和初始化是在 getInitialState 中實現的,而修改則是在 getSelectedColor 方法中完成的(根據選中的 color ID 來獲取 color 物件)。在這裡,所有的 Color 都是 Component 的 props,可以通過 this.props.colors 獲取所有顏色值。

接下來是 ColorBar。在 ColorBar 裡面使用 this.props.colors.map 來遍歷生成了多個 li。它的原理跟 Array.prototype.map 類似,this.props.colors 其實是一個 array,包含了所有的設定的顏色。Map 方法,就是把 array 裡面的 item 取出來傳遞到回撥函式中。回撥函式會把傳進來的 color 值填充到顏色條的模板中。在這個模板的 li 上也繫結了 mouseover 事件,提前把對應的顏色繫結在了 onColorHover 這個屬性方法中,當回撥函式返回了 li 形成一個新的 array 填充到 Color Bar 的 ul 中。

最後讓我們來看一下 ColorDisplay,它是一個負責顯示當前選中顏色的提示框。父級 Component 中的 selectedColor 通過一個 props 傳遞到子級 Component 中並通過 this.props.selectedColor 獲取。 然後子級 Component 通過所獲取到的 selectedColor 值來確定顏色型別(class)決定所需要顯示的文字。

那麼顏色提示是何時更新的呢?答案是當父級 Component 的 state selectedColor 發生更新,也即 setState 方法被呼叫時。當使用者把滑鼠放在某個顏色塊時,就觸發 onColorHover 這個由父節點傳進來的 props 方法,在這個方法內我們會呼叫 setState 方法,根據傳進來的 color id 值重置這個 selectedColor,從而更新 color Display 的顏色提示。

React 預設只要 setState 方法的呼叫,就會更新這個 Component。雖然是虛擬的 Component 的更新,但是有時候我們可以手動的去決定是否有必要更新這個 Component,這時候就可以採用 Component 生命週期裡面的 shouldComponentUpdate 方法。比如說在文章例項中,當使用者先把滑鼠從某一個顏色條上移除,然後又移回到之前的顏色條上時。此時使用者所選擇的還是同一種顏色,即 selectedColor 沒有變化時,我們可以使用 shouldComponentUpdate 來避免 setState 引起的二次更新。判斷當前的 selectedColor.id 和之前的 selectedColor.id 值的區別,此時兩者沒有差異則返回 false,Color Panel 不會被重新渲染了。同理,Color Bar 其實是一個固定的 Component,完全不需要每次根據 selectedColor 來重新渲染,所以我們可以在這個 component 的 shouldComponentUpdate 直接返回

結語

React 的問世把前段開發人員從複雜的資料互動和 UI 渲染不可預測的問題中解放出來,讓開發人員可以將精力專注於資料的變化上。於此同時,React 運用虛擬 DOM 技術,極大的提升了頁面的效能。而且,它通過對模組的劃分,讓應用的邏輯和資料流更加清晰明瞭。相信本文通過對於例項的演示,您可以發現 React 程式碼良好的可讀性和易用性,讓 React 的學習和使用變動輕鬆有趣。

相關文章