Flux
細說Flux做爲一種應用架構(application architecture)或是設計模式(pattern),闡述的是
單向數據流
(a unidirectional data flow)的思想,並非一個框架(framework)或者庫(library)。html
在細說Flux
以前,仍是得提一下React ,畢竟Flux這個名字,是由於它才逐漸進入到大衆視野。node
React
是facebook提出來的一個庫,用來構建用戶界面(U
ser I
nterface),它的三大特色(來自官方):react
JUST THE UI
: 僅僅是一個View(components),能夠認爲是MVC中V
,用來構建UI界面。VIRTUAL DOM
: 虛擬dom,爲的是:高性能dom渲染(利用diff算法)、組件化(向web components看齊)、多端同構(node,react native)。DATA FLOW
: 單向數據流(one-way data flow),指的是:一種自上而下的渲染方式(top-down rendering)。總而言之,對於一個react web應用,它的UI將會由無數個組件(react component)嵌套組合
而成,它們之間存在着層級(hierarchy)關係(經過JSX的語法糖能夠輕易看出),也就所以有了父組件,子組件和頂層組件的概念。git
然而就像上述第一點所說,React僅僅是一個View,對於一個web應用,沒有數據就顯得毫無心義。github
如今,假使咱們經過一個WebAPI模塊取得了數據,那麼如何傳遞給React 組件(components),從而實現UI渲染呢?結合組件的層級關係,想到上述所說的第三點:自上而下的渲染
,咱們將數據傳遞給頂層組件
(controller-view),一樣做爲父組件的它,即可以經過組件的屬性(properties)將一些有用數據傳遞給它的各個子組件(各取所需數據),就這樣一級一級自上而下地傳遞下去(直到每個葉子組件),最終,每個組件都將獲得本身渲染所須要的數據,從而完成UI的渲染。web
那麼,假若此時數據變化了(好比:對於一個列表而言,用戶點擊刪除按鈕,刪除了一條數據),咱們又該如何通知各個組件進行UI更新呢?ajax
有這樣一種清晰的思路:算法
最頂層組件
,從新完成一次自上而下的渲染
(re-render),從而更新了UI(不要過度擔憂性能問題,VIRTUAL DOM
就是用來解決這個的)。顯然上述的幾步,React做爲一個View是不可能作到的,也正由於這樣,Flux做爲一種架構方案才被提出來,它的思想大致就是上述這幾步,經過一個單向數據的流動,完成了UI的更新,用一張圖能夠表示,以下(以Facebook Flux爲例):編程
固然,做爲應用數據處理的模式,除了Flux,還有不少(如:傳統的MVC,MVVM),只是Flux憑藉其單向數據流
特色,使得數據流變得簡單,易於調試和追蹤問題,因此更適合與React進行組合使用。設計模式
前面,咱們就一直在說,Flux是一種架構,一種模式,並非一個框架,也不是一個庫,就像咱們說MVC(VM)的概念同樣,因此,遵循着Flux模式所闡述的思想天然就會出現一些庫,如:Facebook Flux
、Reflux
、Fluxxor
、Redux
等等。
本文主要講解的Reflux,不過在這以前仍是須要先提一下Facebook Flux,從而爲後面一些對比作一些鋪墊。
Facebook Flux,是Facebook在提出Flux架構後,給出的一個對Flux的簡單實現,能夠認爲是Flux庫的第一個範例,因此,也有人稱之爲Original Flux
。
Facebook Flux中引入了四個概念: Dispatcher
、 Actions
、Stores
、Views(Controller-Views)
,而它們之間的關係就如同上面的那張圖所描述的同樣,構成了一個單向數據流的閉環,簡化版以下:
接下來,將以官方的TodoMVC Demo爲例,來講明它們各自的做用,以及它們之間是如何配合工做的?(PS:建議讀者將源代碼clone下來,邊看邊調試)
Facebook Flux中所指的Views,其實就是React Components
,用做UI渲染,而相對特別的,Controller-Views
指的則是頂層React Component
,除了UI渲染外,它還負責接收來自Store變化的數據,並傳遞給它的Child Component(即Controller-View -> Child Views),用於子View的渲染。
在這個例子中,TodoApp就是一個Controller-View,它監聽到TodoStore的數據變化後,便會從新從TodoStore中獲取數據,而後經過調用組件setState()
方法,觸發render()
方法的執行,從而獲得UI的更新(自上而下的渲染)。
// 從TodoStore中獲取數據 function getTodoState() { return { allTodos: TodoStore.getAll(), areAllComplete: TodoStore.areAllComplete() }; } var TodoApp = React.createClass({ componentDidMount: function() { // TodoApp監聽TodoStore的數據變化 TodoStore.addChangeListener(this._onChange); }, render: function() { return ( <div>{/* 此處代碼省去 */}</div> ); }, _onChange: function() { // 從新獲取TodoStore的數據,並經過調用setState,觸發re-render this.setState(getTodoState()); } });
Facebook Flux中的Stores,做爲數據存儲的模塊,相似於MVC中的Model,它負責接收Dispatcher分發過來的actions,針對不一樣的actionType,對數據就進行不一樣的操做(如:增刪改查),最後再通知View,數據變化了,須要進行UI更新。
在這個例子中,TodoStore經過變量_todos變量存儲着整個應用的數據(一個列表),並經過AppDispatcher(Dispatcher實例)註冊回調,來接收不一樣類型的Action指令,進而執行不一樣的數據操做(mutate data),最後通知TodoApp View數據改變,須要更新UI(re-render)。
// 數據存儲(一個列表) var _todos = {}; // 操做數據的函數 function create(text) {/*此處代碼省去*/} function update(id, updates) {/*此處代碼省去*/} function destroy(id) { delete _todos[id]; } // 接收分發過來的Action AppDispatcher.register(function(action) { var text; // 判斷Action類型,採起不一樣的數據操做 switch(action.actionType) { // 新增 case TodoConstants.TODO_CREATE: text = action.text.trim(); if (text !== '') { create(text); // 建立數據,並存儲 TodoStore.emitChange(); // 通知TodoApp數據變化,須要更新UI } break; // 更新 case TodoConstants.TODO_UPDATE_TEXT:/*此處代碼省去*/ break; // 刪除 case TodoConstants.TODO_DESTROY:/*此處代碼省去*/ break; /*此處省去部分代碼*/ } });
Facebook Flux中,Dispatcher起到了一箇中央樞紐(Central Hub)的角色,它存儲着一張Stores列表清單,而且負責Actions的分發工做,即Action的一旦觸發,Dispatcher將會通知列表清單上的全部的Stores,每個Store則選擇性地針對該Action進行特定處理(或者不處理)。
在一個應用中,Dispatcher實例只容許有一個(Single),也就是說它將做爲一個單例而存在。
在這個例子中,AppDispatcher就是這樣一個單例,咱們在TodoStores經過AppDispatcher.register()
註冊回調(見上段代碼),來接收不一樣類型的Actions(消息訂閱),在TodoActions裏經過AppDispatcher.dispatch()
執行不一樣Actions的分發(消息發佈),以下:
var TodoActions = { // 新增Action create: function(text) { AppDispatcher.dispatch({ // 通知TodoStore對數據進行修改(帶有Action類型和關聯數據) actionType: TodoConstants.TODO_CREATE, // Action類型:create text: text // 傳遞給TodoStore的數據 }); }, // 更新Action updateText: function(id, text) { AppDispatcher.dispatch({ actionType: TodoConstants.TODO_UPDATE_TEXT, // Action類型:update id: id, text: text }); }, // 刪除Action destroy: function(id) { AppDispatcher.dispatch({ actionType: TodoConstants.TODO_DESTROY, // Action類型:destroy id: id }); } /*此處省去部分代碼*/ };
Facebook Flux中的有一個概念叫作Action Creator
,能夠將它理解爲一個方法(即helper method
),專門用來建立某種類型的Action。
上一段代碼中,TodoActions模塊就提供了這些helper methods(或者叫作Action Creators),如:
上述每個方法在內部,都定義了本身的常量類型(actionType),而且將接收的參數做爲數據(payload),從而封裝成一個完整的Action(即actionType + payload = Action
)。
最後,再統一經過調用Dispatcher.dispatch()
將特定的Action以消息的形式分發出去(即傳遞給Stores),Stores在獲得Action後,即可以經過Action.actionType
來斷定採起某種操做(或者忽略這個Action),而執行操做時須要用到的數據則來自Action.payload
。
Facebook Flux中提出的這四個概念,承擔着各自角色,經過互相協做,造成了一個單向數據流的閉環。
------【推薦你們看下這篇文章《A cartoon guide to Flux》,生動形象地描述了這幾個角色。】
說完了Facebook Flux,讓咱們靜靜思考一下,存在的不足:
假若,有一個單頁面應用,程序中就可能存在N個store,每一個store都會監聽1~N個action,代碼就會像這樣:
// storeA.js Dispatcher.register(function (action) { switch(action.actionType) { case 'actionA': break; case 'actionB': break; /* ... 1~N個action */ case 'actionN': break; } }); // storeB.js Dispatcher.register(function () { // 同上 }); /* ... */ /* ... 1~N個store */ /* ... */ // storeN.js Dispatcher.register(function () { // 同上 });
假使此時,觸發了一個actionX,那麼storeA~storeB的經過Dispatcher.register()
註冊的回調函數會按註冊順序依次被觸發(無一例外),也就是說每一個store都會獲得actionX通知,惟一不一樣的可能就是:每一個store模塊,會經過各自的switch語句
進行判斷,有的對actionX作處理,有的則不處理(忽略),那麼問題來了:
『既然有些store對actionX不須要處理,那麼它們註冊的回調執行是否有必要?畢竟是函數執行是有開銷的,若是有1000個store對actionX不"感冒"的的話,會不會很浪費資源?』
分析下這個問題:Facebook Flux是以Dispatcher
(發佈者)做爲消息中樞,全部的Action消息都會統一從這裏分發出去,廣播給全部的Store(訂閱者),也就是說:發佈者(Dispatcher)和訂閱者(Stores)之間存在着一對多的關係
,而事實上Actions(消息)和Stores(訂閱者)之間卻存在着一個多對多的關係
,以下圖:
這樣的矛盾,就使得,每個Store不得不在本身的回調函數裏經過Switch語句
,來判斷當前Action的類型,來決定要不要進行處理,那麼暫且拋開性能不說,顯然,這樣寫法,卻顯得繁重且不夠優雅。
因而,接下來,看看Reflux在Facebook Flux的基礎之上,作了那些優化?
Reflux,是另外一個實現Flux模式的庫,旨在使整個應用架構變得更加簡單。
準確地說,Reflux是由Facebook Flux演變而來(inspired by Facebook Flux),能夠說是它的一個進化版本,天然而言就會拿二者進行比較:詳見這裏。
簡要歸納一下重點,就是:
1.Reflux保留了Facebook Flux中原有的三個概念:Actions
、Stores
、Views(Controller-Views)
,去除了Dispatcher
,若是要用一張圖表示的話,就是這樣:
此時會有人問:沒有了消息中樞(Dispatcher),消息Actions如何發佈出去,並傳遞到Stores呢?
答:在Reflux中,每個Action自己就是一個Publisher
(消息發佈者),即自帶了消息發佈功能;而每個Store除了做爲數據存儲以外,它仍是一個Subscriber
,或者叫作Listener
(消息訂閱者),天然就能夠經過監聽Action,來獲取到變化的數據。
2.Store之間能夠互相監聽
這樣的場景仍是有的,好比:在單頁面應用中,若是不一樣Page擁有不一樣的Store,那麼就可能會出現:子頁面Store數據變化後,須要通知到父頁面Store進行相應修改的狀況。
回顧上一節中,對於Facebook Flux的思考,所遺留的問題點,在Reflux中是否解決了呢?
答案是:確定的。
這裏先簡單說明下:
前面講到Actions和Stores(消息訂閱者)間自己就存在着多對多的關係
,而做爲Publisher(消息發佈者),
在Facebook Flux中只有一個,即Dispatcher,因此,不得不在消息發佈時,經過在payload中添加actionType字段來區分消息類型,且Store也所以不得不在回調函數中用Switch語句
進行判斷actionType處理。
而在Reflux中,因爲每個Action都是一個Publisher,且具備特定的含義(actionType),即多個Publisher對應於多個Subscriber(或叫作Listener),Store即可以有目的性地
選擇訂閱想監聽的Action,而不是監聽全部的Action,再經過Switch語句進行篩選;另外,Action(消息)的發佈,也只會通知給以前有訂閱過的Store,而不是全部Store,因此並不會形成任何資源浪費。
歸結一點,就是Reflux將Dispatcher的功能合併到Action中去,使得每個Action都具備了消息發佈的功能,能夠直接被Store所監聽(即listenable)。
不管是從具體的用法,仍是從源碼的架構來看,Reflux本質上能夠理解爲一個PubSub
庫。
能夠用一張具體的圖來表現這一說法,以下:
從圖中能夠看出,Actions
、Stores
和Views
在Reflux中分別承擔着消息發佈訂閱
模式中的一個或多個角色,即:發佈者(Publisher)或者 訂閱者(Subscriber/Listener),也正是基於這樣的角色扮演,才使得它可以實現做爲Flux所應該具備的單向數據流
特性(圖中紅線部分)。
總結一下:
單向數據流
的實現,是徹底基於PubSub
設計模式的。Publisher
,負責消息的分發,通常是由用戶行爲(User Interaction),或是Web API觸發。Publisher
,仍是一個Subscriber
(或者叫作Listener),做爲Subscriber,負責監聽Action的觸發;做爲Publisher,則負責通知View更新UI。Subscriber
,負責監聽Store的數據變化,作到及時更新UI。既然Reflux中的對象不是Publisher就是Subscriber/Listener,那麼代碼是如何組織的呢?
答:Reflux抽取出兩個模塊:PublisherMethods
和 ListenerMethods
,顧名思義,這兩個集合分別存儲着一個對象做爲Publisher
和Listener
所應該具備的方法。
好比:
PublisherMethods中包括:trigger
、triggerAsync
等消息發佈
方法。
ListenerMethods中就包括listenTo
、listenToMany
等消息訂閱
方法。
具體的細節,感興趣的同窗能夠看一下源碼,以及這篇文章《The Reflux data flow model》詳細介紹了Reflux與PubSub的關係。
這一節的主要目的是:經過代碼示例和應用場景,儘量地講解Reflux每一個API的全貌,以及將代碼如何寫得更簡潔優雅?
在Reflux中,由於沒有了Action Creator的概念,因此,Action的建立都是經過統一的API:Reflux.createAction()
或者Reflux.createActions()
來實現。
1.經過Reflux.createAction()
建立單個Action,代碼以下:
// 擁有配置 var action = Reflux.createAction({ actionName: 'addItem', // 其實這個actionName並無什麼用,可不傳 asyncResult: true, sync: false, children: ['success'] }); // 簡化 var action = Reflux.createAction('addItem') // 或者匿名 var addItemAction = Reflux.createAction();
注意:Reflux.createAction()
的返回值是一個特殊的對象 --- 函數
(functor),這樣的設計實際上是爲了方便Action的觸發,顯得更加函數化編程(FRP) ,就像下面這樣使用:
addItemAction({a: 1}); action('hello world', 'Lovesueee');
action建立的時候,能夠進行參數的配置,具體的參數意義以下:
'completed'
和'failed'
的子Action(能夠認爲是設置子Action的一個快捷方式)2.經過Reflux.createActions()
建立多個Action,即Actions集合,代碼以下:
var actions = Reflux.createActions(['addItem', 'deleteItem']); // 個別action配置 var actions = Reflux.createActions(['addItem', { deleteItem: { asyncResult: true, children: ['success'], }, updateItem: {...} }]); // 也能夠這樣 var actions = Reflux.createActions({ addItem: {}, deleteItem: { asyncResult: true, children: ['success'] }, updateItem: {...} });
注意:Reflux.createActions()
返回的是一個普通的對象,即Actions集合,因此Action觸發時,須要指定actionName,就像這樣:
actions.addItem({...}); actions.deleteItem();
通常說來,在實際項目代碼中,因爲涉及到的Action較多,因此通常都是調用Reflux.createActions()
一次性建立Actions集合,比較方便。另外,以後Store經過listenables
字段與Action進行關聯時,須要的也是一個Actions集合。
以前就提到,Action做爲一個Publisher,會擁有PublisherMethods
集合裏提供的一系列方法,這裏統一舉例說明:
var addAction = Reflux.createAction(); addAction.listen(function (url) { // 默認上下文this是addAction $.ajax(url).done(function () { // todo: save to store }); }); addAction('/xxx/add');
trigger 同步
觸發Action消息,在觸發具體的消息以前,首先會先執行preEmit
和shouldEmit
回調。
舉幾個例子說明下,preEmit和shouldEmit的使用,以下:
preEmit用於異步請求,下面兩種方法是等價的:
var actions = Reflux.createActions({ add: { asyncResult: true, preEmit: function (url) { $.ajax(url) .done(this.completed) .fail(this.failed); } } }); // 等價於 var actions = Reflux.createActions({ add: { asyncResult: true } }); actions.add.listen(function(url) { $.ajax(url) .done(this.completed) .fail(this.failed) });
preEmit用於修改payload
var actions = Reflux.createActions(['takePhoto']); // 映射 var maps = { 'photo': { maxSize: 1000 // 從相冊獲取 }, 'camera': { // 拍照 maxSize: 2000, maxSelect: 10 } }; actions.takePhoto.preEmit = function (type) { return maps[type] || maps['photo']; }; actions.takePhoto.listen(function (options) { // do ajax console.log(options); }); actions.takePhoto('photo'); // 或者 // actions.takePhoto('camera');
shouldEmit的使用(防止action的頻繁觸發)
var requesting = false; var actions = Reflux.createActions(['submit']); actions.submit.shouldEmit = function () { return !requesting; } actions.submit.listen(function (url) { requesting = true; $.ajax(url).done(function () { // success }).fail(function () { // error }).always(function () { requesting = false; }); }); // 點擊按鈕 $('#btn').click(function () { actions.submit('url/submit'); });
var addAction = Reflux.createAction({ children: ['completed', 'failed'] // 等價於 asyncResult: true }); addAction.listen(function (url) { var me = this; $.ajax(url).done(function (data) { me.completed(data); }).fail(function () { me.failed(); }); }); // 等價於 addAction.listen(function (url) { this.promise($.ajax(url)); }); addAction('/url/add');
listen
和promise
方法的結合,作了兩件事情:消息訂閱
和異步回調
。好比上面的例子,就能夠這樣簡寫:
addAction.listenAndPromise(function(url) { return $.ajax(url); // 注意:返回promise對象 });
setTimeout(function () {action();}, 0)
。改寫上面的例子,以下:
var addAction = Reflux.createAction({ asyncResult: true }); addAction.listenAndPromise(function(url) { return $.ajax(url); // 注意:返回promise對象 }); // 觸發消息,監聽異步子action的成功與失敗 // action這裏能夠獲取到數據, addAction.triggerPromise('/url/add').then(function (data) { console.log(data); }, function () { console.log('failed'); });
最後再說說,子Action
的概念,其實以前都用到了,主要是用於異步請求,成功和失敗回調的執行,這裏簡單說明一下:
在利用Reflux.createAction
建立Action之初,能夠經過下面的兩種方式建立子Action
:
var addAction = Reflux.createAction({ asyncResult: true }); // 等價於 var addAction = Reflux.createAction({ children: ['completed', 'failed'] });
在建立以後這兩個子Action在數據存儲結構中,即可以直接經過addAction.completed
和addAction.failed
訪問。
Store做爲數據存儲中心,且由於介於Actions和Views之間,因此同時承擔着Publisher(消息發佈者)和Subscriber(消息訂閱者)兩種角色。
Reflux中,Store的建立一樣是經過提供的API:Reflux.createStore()
,就像下面這樣:
var action = Reflux.createAction(); var store = Reflux.createStore({ init: function () { // 存儲數據 this.data = {}; // Action監聽 this.listenTo(action, this._onAction); // 或者 // this.listenTo(action, '_onAction'); // 或者 // action.listen(this._onAction); }, _onAction: function (msg) { console.log(msg); } }); action('hello world'); // 觸發動做
不一樣於Action,Store返回的是一個普通的對象,一般咱們會在init
方法中進行數據的存儲
和Action的監聽
。
在建立Store時,咱們能夠經過傳遞一個特殊的字段mixins
,它的功能就有點相似於React Component中的mixins。
在mixin中,對於幾個特殊方法:init
, preEmit
, shouldEmit
會進行特殊處理(組合),保證mixins裏面的方法都會被執行而,對於其餘自定義方法,有必定的覆蓋規則,好比,下面的例子中myMethod
方法的覆蓋優先級就是:store > mixin3 > mixin2 > mixin。
var mixin = { init: function () { console.log('mixin:init') }, myMethod: function () { console.log('mixin.myMethod'); } }; var mixin2 = { init: function () { console.log('mixin2:init') }, myMethod: function () { console.log('mixin2.myMethod'); } }; var mixin3 = { mixins: [mixin2], init: function () { console.log('mixin3:init') }, myMethod: function () { console.log('mixin3.myMethod'); }, otherMethod: function () { console.log('mixin3.otherMethod'); } }; var store = Reflux.createStore({ mixins: [mixin, mixin3], init: function () { console.log('store:init'); }, myMethod: function () { console.log('store:myMethod'); } }); store.myMethod(); // mixin:init // mixin2:init // mixin3:init // store:init // store:myMethod
再從PubSub的角度說說Store:
做爲消息的發佈者,擁有着和Action同樣的能力,即擁有PublisherMethods
集合的全部方法;同時做爲消息的訂閱者,用來監聽Action的觸發(或其餘Store的改變),從而改變自身數據,Store還擁有ListenerMehthods
集合提供的方法。
這裏重點說一下,Store做爲消息訂閱者這個角色,擁有的幾個比較重要的方法:
舉例幾個例子,說明:
Store監聽Action
var addAction = Reflux.createAction('add'); var store = Reflux.createStore({ init: function () { this.data = { flag: false }; }, getInitialState: function () { return this.data; } }); store.listenTo(addAction, function (flag) { this.data.flag = flag; }); addAction(true);
Store監聽其餘Store(設置listenTo第三個回調,經過調用被監聽Store的getInitialState方法獲取其初始值)
var storeA = Reflux.createStore({ init: function () { this.data = { a: 1 }; }, getInitialState: function () { return this.data; } }); var storeB = Reflux.createStore({ init: function () { this.data = { b: 2 }; } }); storeB.listenTo(storeA, function (a) { this.data.a = a; }, function (data) { // storeB獲取storeA的初始值 this.data.a = data.a; }); console.log(storeB); // storeB.data => {a: 1, b: 2} storeA.trigger(3); console.log(storeB); // storeB.data => {a: 3, b: 2}
一般會這樣使用:
var actions = Reflux.createActions(['addItem', 'deleteItem']); var store = Reflux.createStore({ init: function () { this.items = []; this.listenToMany(actions); }, onAddItem: function (item) { this.items.push(item); }, onDeleteItem: function (item) { var items = this.items; items.forEach(function (val, index) { if (val === item) { items.splice(index, 1); // todo: break } }); } }); actions.addItem(1); actions.addItem(2); console.log(store); // store.items => [1, 2] actions.deleteItem(1); console.log(store); // store.items => [2]
當一個store監聽listenables對象集合(即多個監聽對象,好比:多個action)時,實際上作的事情也仍是單個消息訂閱store.listenTo(actionName, onActionName)
,可是這裏有一個約定(或者叫作映射關係),以上面的兩個action爲例:
actionName | onActionName |
---|---|
addItem | onAddItem |
deleteItem | onDeleteItem |
actionName 對應的回調就是 on + actionName
(駝峯寫法)
而後Reflux還作了一些容錯處理,若是你不按照這個約定(即命名不規範)的話,它會這樣獲取須要註冊的回調:
以名爲addItem
action爲例,它的callback依次會取:
this.onAddItem -> this.addItem -> undefined(不註冊回調)
天然而然,涉及到listenTo
方法就會想起上面說的它的第三個參數defaultCallback
用來初始化,那麼在listenToMany
方法對此就有這樣的約定(或者叫作映射關係):
以名爲addItem
action爲例(通常是store之間纔會使用,且不多使用),它的defaultCallback依次會取:
this.onAddItemDefault -> this.addItemDefault -> undefined(沒有初始化回調)
這裏還須要再提起一次,子Action的概念,對於下面這段代碼:
以前會這樣作:
var addAction = Reflux.createAction({ asyncResult: true }); var store = Reflux.createStore({ init: function () { this.listenTo(addAction.completed, 'onAddCompleted'); this.listenTo(addAction.failed, 'onAddFailed'); }, onAddCompleted: function (data) { console.log('completed: ', data); }, onAddFailed: function () { console.log('failed') } });
若是用listenToMany方法來作的話,就能夠這樣簡化:
var addAction = Reflux.createAction({ asyncResult: true }); var store = Reflux.createStore({ init: function () { this.listenToMany({add: addAction}); // 注意:參數是一個對象 }, onAddCompleted: function (data) { console.log('completed: ', data); }, onAddFailed: function () { console.log('failed') } });
也就是說,listenToMany
方法,不但關聯了action,還會關聯它的子action,即addAction.completed
和addAction.failed
,這裏就又有一個約定(或者叫作映射關係):
actionName | onActionName | childActionName | onChildActionName |
---|---|---|---|
add | onAdd | addCompleted / addFailed | onAddCompleted / onAddFailed |
即:on + 主action名 + 子action名(駝峯)
然而,在利用Reflux.createStore()
建立之初,咱們能夠利用更簡潔的一種方式,對Store和Actions進行關聯。
以前是這樣:
var actions = Reflux.createActions(['addItem', 'deleteItem']); var store = Reflux.createStore({ init: function () { this.listenToMany(actions); // 關聯actions }, onAddItem: function () { // todo: add }, onDeleteItem: function () { // todo: delete } });
如今能夠經過listenables
字段來關聯:
var actions = Reflux.createActions(['addItem', 'deleteItem']); var store = Reflux.createStore({ listenables: actions // 關聯actions init: function () { // init }, onAddItem: function () { // todo: add }, onDeleteItem: function () { // todo: delete } });
這是一種快捷方式,其實內部原理就是store在建立的時候,調用了listenToMany
方法。
注意:listenables這裏能夠是actions組成的數組,如:[actions1, actions2],就至關於多調用幾回listenToMany
方法,如:
this.listenToMany(actions1); this.listenToMany(actions2);
對於View,只需在React Component裏的生命週期函數裏,負責監聽Store的變化,並及時經過調用setState()
方法更新UI便可,就像下面這樣:
var myStore = Reflux.createStore({ init: function () { // init } }); class MyComponent extends React.Component { componentDidMount() { this.unsubscribe = myStore.listen(this.onChange); } componentWillUnmount: function() { this.unsubscribe(); // 注意:在組件銷燬時,必定要解除監聽 } onChange(data) { this.setState(data); // re-render } }
上述方式,是經過myStore.listen()
來進行消息訂閱的,而實際上,View自己並無消息訂閱的能力,因此Reflux提供了一個mixin,叫作Reflux.ListenerMixin
。
它的實現是這樣的:
module.exports = _.extend({ componentWillUnmount: ListenerMethods.stopListeningToAll }, ListenerMethods);
做爲React Component的一個mixin,它其實作了兩件事情:
ListenerMethods
集合裏的方法,使View具有了消息訂閱的能力。componentWillUnmount
生命週期方法裏,對以前監聽的Action自動解綁。因此,上述代碼能夠簡化爲:
import Reflux from 'reflux'; import ReactMixin from 'react-mixin'; class MyComponent extends React.Component { componentDidMount() { this.listenTo(myStore, this.onChange); // View自己具有了訂閱的能力 } componentWillUnmount: function() { // nothing 無需手動解除監聽 } onChange(data) { this.setState(data); // re-render } } // ES6 mixin寫法 ReactMixin.onClass(MyComponent, Reflux.ListenerMixin);
然而還有更簡單的寫法,就是經過Reflux.connect()
來寫,以下:
import Reflux from 'reflux'; import ReactMixin from 'react-mixin'; class MyComponent extends React.Component { componentDidMount() { // nothing 無需手動監聽 } componentWillUnmount: function() { // nothing 無需手動解除監聽 } onChange(data) { // noting 無需手動setState } } // ES6 mixin寫法 ReactMixin.onClass(MyComponent, Reflux.connect(myStore));
原理是這樣的,React.connect(myStore)
返回的一個mixin,這個mixin內部在作了相似下面的事情:
this.listenTo(myStore, (data) => { this.setState(data); });
因此,這才幫咱們省去了手動監聽
,手動刪除監聽
,還有手動觸發UI更新
這三步。
以上就是本人對Flux以及Reflux的一些理解和使用小結,不對的地方還請指出。
原創文章,轉載請說明出處:http://www.cnblogs.com/lovesueee/p/4893218.html