你為什么應該試一試Reflux?
一點背景
React在設計之初就只關注在View本身上,其余部分如 數據的獲取
, 事件處理
等,全然不在考慮之內。不過構建大型的Web前端應用,這些點又實在不可避免。所以非死book的工程師提出了前端的 Flux
架構,這個架構的最大特點是 單向數據流
(后面詳述)。但是 Flux
本身的實現有很多不合理的地方,比如單例的Dispatcher會在系統中有多種事件時導致臃腫的 switch-cases
等。
這里是非死book官方提供的提供 Flux的結構圖 。
其實整個Flux背后的思想也 不是什么新東西 。在很久之前,Win32的消息機制(以及很多的GUI系統)就在使用這個模型,而且這也是一種被證實可以用來構建大型軟件的模型。
鑒于Flux本身只是一個架構,而且非死book提供的參考實現又有一些問題,所以社區有了很多版本的Flux實現。比如我們這里會用到的 Reflux 。
Reflux簡介
簡而言之, Reflux 里有兩個組件:Store和Action。Store負責和數據相關的內容:從服務器上獲取數據,并更新與其綁定的React組件(view controller);Action是一個事件的集合。Action和Store通過convention來連接起來。
具體來說,一個典型的過程是:
- 用戶的動作(或者定時器)在組件上觸發一個Action
- Reflux會調用對應的Store上的callback(自動觸發)
- 這個callback在執行結束之后,會顯式的觸發(trigger)一個數據
- 對應的組件(可能是多個)的state會被更新
- React組件檢測到state的變化后,會自動重繪自身 </ol>
- 一個用來
fetch
數據,存儲數據的store (BookmarkStore) - 一個用來表達事件的
Action
(BookmarkActions) - 一個列表組件(BookmarkList)
- 一個組件條目組件(Bookmark) </ol>
- 一個SearchBox組件
- 一個新的
Action
:search
BookmarkStore
上的一個新方法onSearch
- 組件
SearchBox
需要和BookmarkActions
關聯起來
</ol>
一個例子
我們這里將使用React/Reflux開發一個實際的例子,從最簡單的功能開始,逐步將其構建為一個較為復雜的應用。
這個應用是一個書簽展示應用(數據來源于我的Google Bookmarks)。第一個版本的界面是這樣的:
要構建這樣一個列表應用,我們需要這樣幾個部分:
定義Actions
var Reflux = require('reflux'); var BookmarkActions = Reflux.createActions([ 'fetch' ]);module.exports = BookmarkActions;</pre>
第一個版本,我們只需要定義一個
fetch
事件即可。然后在Store
中編寫這個Action的回調:定義Store
var $ = require('jquery'); var Reflux = require('reflux'); var BookmarkActions = require('../actions/bookmark-actions');var Utils = require('../utils/fetch-client');
var BookmarkStore = Reflux.createStore({ listenables: [BookmarkActions],
init: function() { this.onFetch(); },
onFetch: function() { var self = this; Utils.fetch('/bookmarks').then(function(bookmarks) { self.trigger({ data: bookmarks, match: '' }); }); } });
module.exports = BookmarkStore;</pre>
此處,我們使用
listenables: [BookmarkActions]
來將Store和Action關聯起來,根據convention
,on
+事件名稱就是回調函數的名稱。這樣當Action
被觸發的時候,onFetch
會被調用。當獲取到數據之后,這里會顯式的trigger
一個數據。List組件
var React = require('react'); var Reflux = require('reflux');var BookmarkStore = require('../stores/bookmark-store.js'); var Bookmark = require('./bookmark.js');
var BookmarkList = React.createClass({ mixins: [Reflux.connect(BookmarkStore, 'bookmarks')],
getInitialState: function() { return { bookmarks: {data: []} } },
render: function() { var list = []; this.state.bookmarks.data.forEach(function(item) { list.push(<Bookmark title={item.title} created={item.created}/>) });
return <ul> {list} </ul>
} });
module.exports = BookmarkList;</pre>
在組件中,我們通過
mixins: [Reflux.connect(BookmarkStore, 'bookmarks')]
將Store和組件關聯起來,這樣List組件state
上的bookmarks
就和BookmarkStore
連接起來了。當state.bookmarks
變化之后,render
方法就會被自動調用。對于每一個書簽,就只是簡單的展示內容即可:
Bookmark組件
var React = require('react'); var Reflux = require('reflux'); var moment = require('moment');var Bookmark = React.createClass({
render: function() { var created = new Date(this.props.created * 1000); var date = moment(created).format('YYYY-MM-DD');
return <li> <div className='bookmark'> <h5 className='title'>{this.props.title}</h5> <span className='date'>Created @ {date}</span> </div> </li>;
} });
module.exports = Bookmark;</pre>
這里我使用了
moment
庫將unix timestamp
轉換為日期字符串,然后寫在頁面上。最后,
Utils
只是一個對jQuery
的包裝:var $ = require('jquery'); var Promise = require('promise');module.exports = { fetch: function(url) { var promise = new Promise(function (resolve, reject) { $.get(url).done(resolve).fail(reject); });
return promise; }
}</pre>
我們再來總結一下,
BookmarkStore
在初始化的時候,顯式地調用了onFetch
,這個動作會導致BookmarkList
組件的state
的更新,這個更新會導致BookmarkList
的重繪,BookmarkList
會依次迭代所有的Bookmark
。更復雜一些
當然,Reflux的好處不僅僅是上面描述的這種單向數據流。當
Store
,Actions
以及具體的組件
被解耦之后,構建大型的應用才能成為可能。我們來對上面的應用做一下擴展:我們為列表添加一個搜索功能。隨著用戶的鍵入,我們發送請求到服務器,將過濾后的數據渲染為新的列表。我們需要這樣幾個東西
為了讓用戶看到匹配的效果,我們需要將匹配到的關鍵字高亮起來,這樣我們需要在
Bookmark
組件中監聽BookmarkStore
,當BookmarkStore
發生變化之后,我們就可以即時修改書簽的title
了。搜索框組件
var React = require('react'); var BookmarkActions = require('../actions/bookmark-actions');var SearchBox = React.createClass({ performSearch: function() { var keyword = this.refs.keyword.value; BookmarkActions.search(keyword); },
render: function() { return <div className="search"> <input type='text' placeholder='type to search...' ref="keyword" onChange={this.performSearch} />
</div> } });module.exports = SearchBox;</pre>
BookmarkStore
var $ = require('jquery'); var Reflux = require('reflux'); var BookmarkActions = require('../actions/bookmark-actions');var Utils = require('../utils/fetch-client');
var BookmarkStore = Reflux.createStore({ listenables: [BookmarkActions],
init: function() { this.onFetch(); },
onFetch: function() { var self = this; Utils.fetch('/bookmarks').then(function(bookmarks) { self.trigger({ data: bookmarks, match: '' }); }); },
onSearch: function(keyword) { var self = this;
Utils.fetch('/bookmarks?keyword='+keyword).then(function(bookmarks) { self.trigger({ data: bookmarks, match: keyword }); });
} });
module.exports = BookmarkStore;</pre>
我們在
BookmarkStore
中添加了onSearch
方法,它會根據關鍵字來調用后臺API進行搜索,并將結果trigger
出去。由于數據本身的結構并沒有變化(只是數量會由于過濾而變少),因此BookmarkList
是無需修改的。書簽高亮
當搜索匹配之后,我們可以將對應的關鍵字高亮起來,這時候我們需要修改
Bookmark
組件:var React = require('react'); var Reflux = require('reflux'); var moment = require('moment');var BookmarkStore = require('../stores/bookmark-store.js');
var Bookmark = React.createClass({ mixins: [Reflux.listenTo(BookmarkStore, 'onMatch')],
onMatch: function(data) { this.setState({ match: data.match }); },
getInitialState: function() { return { match: '' } },
render: function() { var created = new Date(this.props.created * 1000); var date = moment(created).format('YYYY-MM-DD');
var title = this.props.title; if(this.state.match.length > 0) { title = <span dangerouslySetInnerHTML={{ __html : this.props.title.replace(new RegExp('('+this.state.match+')', "gi"), '<span class="highlight">$1</span>') }} /> } return <li> <div className='bookmark'> <h5 className='title'>{title}</h5> <span className='date'>Created @ {date}</span> </div> </li>;
} });
module.exports = Bookmark;</pre>
mixins: [Reflux.listenTo(BookmarkStore, 'onMatch')]
表示,我們需要監聽BookmarkStore
的變化,當變化發生時,調用OnMatch
方法。OnMatch
會修改組件的match
屬性,從而觸發render
。在
render
中,我們替換關鍵字為<span class="highlight">$keyword</span>
,這樣就可以達到預期的效果了:
![]()
結論
從上面的例子可以看到,我們從一開始就引入了Reflux。雖然第一個版本和React原生的寫法差異并不是很大,但是當加入
SearchBox
功能之后,需要修改的地方非常清晰:添加Actions
,在對應的Store
中添加callback,然后在組件中使用。這種方法不僅可以最大程度的使用React
的長處(diff render),而且使得代碼邏輯變得較為清晰。隨著業務代碼的不斷增加,Reflux提供的方式確實可以在一定程度上控制代碼的復雜性和可讀性。
完整的 代碼地址在這里 。
其他參考
本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!