標籤:
React,來自 Facebook,是一個用來建立使用者介面的非常優秀的類庫。唯一的問題是 React 不會關注於你的應用如何處理資料。大多數人把 React 當做 MV* 中的 V。所以,Facebook 引入了一種稱作 Flux 的模式,提供了一個功能上的通道,可用於應用內的資料處理。這個教程簡短的介紹了 Flux 模式並且展示了如何使用 React 和 Flux 架構搭建一個記事本應用。
Flux 入門
Flux 依賴於一個單的資料流。在這個 Flux 模式中有兩個關鍵的組件:
Stores :一個 store 組件,恰如其名,儲存這應用的資料。
Actions :新的資料通過 actions 流入 stores。當 actions 被調用時, Stores 監聽 actions 並且會做一些反饋(比如修改資料)。這保證了資料的單向性。
為了增強這個概念,我們做一個實實在在的例子。比如,在一個記事本應用中你可能會有下面的安排:
一個叫做 NoteStore 的 store 用來儲存日記列表。
要有一個叫做 createNote 的 action。NoteStore 監聽到 createNode 的 action 然後當 action 被調用的時候用一個新的日記來更新列表。資料僅僅能通過 action 流入到 store 中。
每 次資料發生變化時 NoteStore 觸發一個事件。你的 React 組件,假如叫做 NodeListComponent,監聽這個事件然後更新存在於 view 層中日記的列表。這就是從 store 流出的資料的流動方式。所以,資料流可以形象化的看做下面這樣:
Flux 模式最大的優勢就是它保證了應用中資料的平緩。比如,任何資料的變化只能通過 action 發出,這也就更加容易理解如何做到一旦資料變化就會影響整個應用了。
注意:
如果你看過了 Facebook 關於 Flux 的指南,你一定會注意到 Dispatcher 的概念。Dispatcher 是一個註冊到 store 中的一個回呼函數。當 action 被調用,Dispatcher 會響應它並且把相關的資料發送到所有註冊的 store 中。Store 然後檢查 action 的類型並且作出相應的反應。
上面的過程被一個叫做 Reflux 的類庫很好的簡化了。它通過使 actions 可監聽去掉了 Dispatcher 的概念。所以,在 Reflux 中,store 可以直接監聽 action 並且對他們需要的內容做出響應。
為了更好地理解 Flux 模式,我們使用 Reflux,React 和 Node.js 共同建立一個記事本應用。
搭建開發環境
我們會使用 React 和 Reflux 作為 Node 模組並且使用 Browserify 讓它們在用戶端同樣可用。下面就介紹如何搭建環境:
我們會使用 Browserify 打包我們的 React 組件,Actions 和 Stores 被打包成一個用戶端 .js 包。
我們會使用 grunt watch 監控上面的組件中的變化並且每次發生變化時都會重新運行 Browserify。
grunt nodemon 被用於重啟服務每當任何 .jsx 或者 .js 檔案發生變化時,所以你不需要手動控制。
你可以從 GitHub 下載相關代碼然後開啟 Gruntfile.js 查看相關的任務。當你的機器上有了這個庫,你僅僅需要運行 npm install 來安裝所以來的所有 node 模組。運行下面的命令,然後開始開發:
grunt watchgrunt nodemon
這個應用可以在 localhost:8000 訪問到。
使用這個應用
我們從這個應用不同的組件開始。下面是我們如何將我們的 UI 分成不同的組件:
下面是每個組件的功能:
NoteApp:這是根組件,包含了兩個子組件:NoteListBox 和 NoteCreationBox。
NoteListBox:有一個子組件 NoteList。它會得到一個日記列表從 Flux Store 並且把它們傳遞到 NoteList。
NoteList:負責渲染每個 Note 組件。傳遞一個 note 對象到每個 Note 組件中。
Note:為一個單獨的 note 項呈現具體內容。在這個例子中僅僅展示 title。你可以輕易的進入並且展示其他的細節像 date,subtitle 等。
NoteCreationBox:這個組件渲染了一個 TextArea 組件,並且如果存在一個當前正在編輯的 日記 id,則傳遞給它。
TextArea:提供一個 textarea 用於使用者輸入。傳遞日記的文本到 NoteCreationBox 中儲存。
建立 Actions
我們使用 Reflux 建立一個 action。如果你開啟了 actions/NoteActions.js,你會看到 action 是如何被建立的。下面是程式碼片段:
123 |
var Reflux = require( ‘reflux‘ ); var NoteActions = Reflux.createActions([ ‘createNote‘ , ‘editNote‘ ]);module.exports = NoteActions; |
Reflux.createActions 被用作建立 Action。我們匯出這些 Action ,在組件中方便使用它們。
建立 Store
我們建立了一個叫做 NoteStore 的 store,用來維護日記隊列。下面的代碼用於建立 store(stores/NoteStore.js):
123456789101112131415161718192021222324252627282930313233343536 |
var Reflux = require( ‘reflux‘ ); var NoteActions = require( ‘../actions/NoteActions‘ ); var _notes = []; //This is private notes arrayvar NoteStore = Reflux.createStore({ init: function () { // Here we listen to actions and register callbacks this .listenTo(NoteActions.createNote, this .onCreate); this .listenTo(NoteActions.editNote, this .onEdit); }, onCreate: function (note) { _notes.push(note); //create a new note // Trigger an event once done so that our components can update. Also pass the modified list of notes. this .trigger(_notes); }, onEdit: function (note) { // Update the particular note item with new text. for ( var i = 0; i < _notes.length; i++) { if (_notes[i]._id === note._id) { _notes[i].text = note.text; this .trigger(_notes); break ; } } }, //getter for notes getNotes: function () { return _notes; }, //getter for finding a single note by id getNote: function (id) { for ( var i = 0; i < _notes.length; i++) { if (_notes[i]._id === id) { return _notes[i]; } } }});module.exports = NoteStore; //Finally, export the Store |
可以看到,我們監聽兩個 action,createNote 和 editNote,在 init 方法中。同樣,我們註冊了回呼函數當 action 被調用時。添加/更新日記的代碼很簡單。我們暴漏了 getter 用來擷取日記列表。最後,store 被暴漏以便它可以在我們組件中使用。
建立組件
我們所有的 React 組件都位於 react/components 目錄下。我已經展示了 UI 的所有結構。你可以看看下載的原始碼,詳細瞭解每個組件。這裡我展示看最關鍵的一部分(例:我們的組件如何調用 action 以及如何與 store 互動)
NoteListBox:
這個組件從 NoteStore 獲得了日記列表,並且把它們吐給 NoteList 組件然後渲染日記。這個組件看起來像下面這樣:
123456789101112131415161718192021222324252627282930 |
var React = require( ‘react‘ ); var NoteList = require( ‘./NoteList.jsx‘ ); var NoteStore = require( ‘../../stores/NoteStore‘ ); var NoteListBox = React.createClass({ getInitialState: function () { return { notes: NoteStore.getNotes() }; }, onChange: function (notes) { this .setState({ notes: notes }); }, componentDidMount: function () { this .unsubscribe = NoteStore.listen( this .onChange); }, componentWillUnmount: function () { this .unsubscribe(); }, render: function () { return ( <div className= "col-md-4" > <div className= "centered" ><a href= "" onClick={ this .onAdd}>Add New</a></div> <NoteList ref= "noteList" notes={ this .state.notes} onEdit={ this .props.onEdit} /> </div> ); } }); module.exports = NoteListBox; |
當這個組件開始工作,我們就可以開始監聽 NoteStore 的 change 事件。無論何時在這個日記列表中有變化時都會去廣播。我們的組件監聽這個事件以便它可以重新渲染這個日記當有任何的變化時。下面這行代碼註冊一個監聽器:
this.unsubscribe = NoteStore.listen(this.onChange);
因此,無論什麼時候發生了變化,組件的 onChange 方法都會被調用。這個方法接收到一個更新的日記列表然後改變 state。
123 |
this .setState({ notes: notes //state changes }); |
由於 this.state.notes 是作為一個 prop 被傳遞到 NoteList 中,只要 state 發生了變化 NoteList 都會重新渲染自己。
最後,我們在 componentWillUnmount 中添加了 this.unsubscribe() 用來移除監聽器。
因此,這就是 NoteList 如何通過監聽 Store 的 change 事件一直處於最新的原因。現在我們看一下一個日記如何被建立/編輯。
NoteCreationBox:
仔細看一下 NoteCreationBox 方法:
123456 |
handleSave: function (noteText, id) { if (id) { NoteActions.editNote({ _id: id, text: noteText }); } else { NoteActions.createNote({ _id: Date.now(), text: noteText }); }} |
每當儲存按鈕被點擊時這個方法都會被調用。它接受 noteText 作為它的第一個參數。如果 id 作為第二個參數被傳遞,我們知道這是一個編輯操作並且調用 NoteActions.editNote()。否則我們會為新的日記產生一個 id 並且調用NoteActions.createNote() 方法。記住 NoteStore 監聽了這些 action。根據這個 action,正確的 store 回調被執行。一旦資料發生了更改,store 會觸發一個 change 事件並且我們的 NoteList 組件會更新自己。
這就是在 Flux 應用中資料如何流入系統並且隨後流出的。
為什麼在服務端使用 React
你 可能好奇為什麼在服務端使用 React 和 Reflux。React 最酷的一個特性就是可以在用戶端和服務端渲染。使用這種技術,你可以建立同構的應用,在服務端渲染並且表現的和單頁面應用一樣。然而這對一個記事本應用不 是很必要,你可以輕鬆地使用這個方案建立一個複雜的同構的應用。
使用 React 和 Flux 建立一個記事本應用