在實際項目中,應用每每充斥着大量的異步操做,如ajax請求,定時器等。一旦應用涉及異步操做,代碼便會變得複雜起來。在flux體系中,讓人困惑的每每有幾點:node
異步操做應該在actions仍是store中進行?git
異步操做的多個狀態,如pending(處理中)、completed(成功)、failed(失敗),該如何拆解維護?github
請求參數校驗:應該在actions仍是store中進行校驗?校驗的邏輯如何跟業務邏輯自己進行分離?ajax
本文從簡單的同步請求講起,逐個對上面3個問題進行回答。一家之言並不是定則,讀者可自行判別。shell
本文適合對reflux有必定了解的讀者,如尚無瞭解,可先行查看 官方文檔 。本文所涉及的代碼示例,可在 此處下載。架構
同步操做比較簡單,沒什麼好講的,直接上代碼可能更直觀。異步
var Reflux = require('reflux'); var TodoActions = Reflux.createActions({ addTodo: {sync: true} }); var state = []; var TodoStore = Reflux.createStore({ listenables: [TodoActions], onAddTodo: function(text){ state.push(text); this.trigger(state); }, getState: function(){ return state; } }); TodoStore.listen(function(state){ console.log('state is: ' + state); }); TodoActions.addTodo('起牀'); TodoActions.addTodo('吃早餐'); TodoActions.addTodo('上班');
看下運行結果async
➜ examples git:(master) ✗ node 01-sync-actions.js state is: 起牀 state is: 起牀,吃早餐 state is: 起牀,吃早餐,上班
下面是個簡單的異步操做的例子。這裏經過addToServer
這個方法來模擬異步請求,並經過isSucc
字段來控制請求的狀態爲成功仍是失敗。單元測試
能夠看到,這裏對前面例子中的state
進行了必定的改造,經過state.status
來保存請求的狀態,包括:測試
pending:請求處理中
completed:請求處理成功
failed:請求處理失敗
var Reflux = require('reflux'); /** * @param {String} options.text * @param {Boolean} options.isSucc 是否成功 * @param {Function} options.callback 異步回調 * @param {Number} options.delay 異步延遲的時間 */ var addToServer = function(options){ var ret = {code: 0, text: options.text, msg: '添加成功 :)'}; if(!options.isSucc){ ret = {code: -1, msg: '添加失敗!'}; } setTimeout(function(){ options.callback && options.callback(ret); }, options.delay); }; var TodoActions = Reflux.createActions(['addTodo']); var state = { items: [], status: '' }; var TodoStore = Reflux.createStore({ init: function(){ state.items.push('睡覺'); }, listenables: [TodoActions], onAddTodo: function(text, isSucc){ var that = this; state.status = 'pending'; that.trigger(state); addToServer({ text: text, isSucc: isSucc, delay: 500, callback: function(ret){ if(ret.code===0){ state.status = 'success'; state.items.push(text); }else{ state.status = 'error'; } that.trigger(state); } }); }, getState: function(){ return state; } }); TodoStore.listen(function(state){ console.log('status is: ' + state.status + ', current todos is: ' + state.items); }); TodoActions.addTodo('起牀', true); TodoActions.addTodo('吃早餐', false); TodoActions.addTodo('上班', true);
看下運行結果:
➜ examples git:(master) ✗ node 02-async-actions-in-store.js status is: pending, current todos is: 睡覺 status is: pending, current todos is: 睡覺 status is: pending, current todos is: 睡覺 status is: success, current todos is: 睡覺,起牀 status is: error, current todos is: 睡覺,起牀 status is: success, current todos is: 睡覺,起牀,上班
首先,祭出官方flux架構示意圖,相信你們對這張圖已經很熟悉了。flux架構最大的特色就是單向數據流,它的好處在於 可預測、易測試。
一旦將異步邏輯引入store,單向數據流被打破,應用的行爲相對變得難以預測,同時單元測試的難度也會有所增長。
ps:在大部分狀況下,將異步操做放在store裏,簡單粗暴有效,反而能夠節省很多代碼,看着也直觀。究竟放在actions、store裏,筆者是傾向於放在actions
裏的,讀者可自行斟酌。
畢竟,社區對這個事情也還在吵個不停。。。
仍是前面的例子,稍做改造,將異步的邏輯挪到actions
裏,二話不說上代碼。
reflux是比較接地氣的flux實現,充分考慮到了異步操做的場景。定義action時,經過asyncResult: true
標識:
操做是異步的。
異步操做是分狀態(生命週期)的,默認的有completed
、failed
。能夠經過children
參數自定義請求狀態。
在store裏經過相似onAddTodo
、onAddTodoCompleted
、onAddTodoFailed
對請求的不一樣的狀態進行處理。
var Reflux = require('reflux'); /** * @param {String} options.text * @param {Boolean} options.isSucc 是否成功 * @param {Function} options.callback 異步回調 * @param {Number} options.delay 異步延遲的時間 */ var addToServer = function(options){ var ret = {code: 0, text: options.text, msg: '添加成功 :)'}; if(!options.isSucc){ ret = {code: -1, msg: '添加失敗!'}; } setTimeout(function(){ options.callback && options.callback(ret); }, options.delay); }; var TodoActions = Reflux.createActions({ addTodo: {asyncResult: true} }); TodoActions.addTodo.listen(function(text, isSucc){ var that = this; addToServer({ text: text, isSucc: isSucc, delay: 500, callback: function(ret){ if(ret.code===0){ that.completed(ret); }else{ that.failed(ret); } } }); }); var state = { items: [], status: '' }; var TodoStore = Reflux.createStore({ init: function(){ state.items.push('睡覺'); }, listenables: [TodoActions], onAddTodo: function(text, isSucc){ var that = this; state.status = 'pending'; this.trigger(state); }, onAddTodoCompleted: function(ret){ state.status = 'success'; state.items.push(ret.text); this.trigger(state); }, onAddTodoFailed: function(ret){ state.status = 'error'; this.trigger(state); }, getState: function(){ return state; } }); TodoStore.listen(function(state){ console.log('status is: ' + state.status + ', current todos is: ' + state.items); }); TodoActions.addTodo('起牀', true); TodoActions.addTodo('吃早餐', false); TodoActions.addTodo('上班', true);
運行,看程序輸出
➜ examples git:(master) ✗ node 03-async-actions-in-action.js status is: pending, current todos is: 睡覺 status is: pending, current todos is: 睡覺 status is: pending, current todos is: 睡覺 status is: success, current todos is: 睡覺,起牀 status is: error, current todos is: 睡覺,起牀 status is: success, current todos is: 睡覺,起牀,上班
前面已經示範瞭如何在actions裏進行異步請求,接下來簡單演示下異步請求的前置步驟:參數校驗。
預期中的流程是:
流程1:參數校驗 --> 校驗經過 --> 請求處理中 --> 請求處理成功(失敗)
流程2:參數校驗 --> 校驗不經過 --> 請求處理失敗
直接對上一小節的代碼進行調整。首先判斷傳入的text
參數是不是字符串,若是不是,直接進入錯誤處理。
var Reflux = require('reflux'); /** * @param {String} options.text * @param {Boolean} options.isSucc 是否成功 * @param {Function} options.callback 異步回調 * @param {Number} options.delay 異步延遲的時間 */ var addToServer = function(options){ var ret = {code: 0, text: options.text, msg: '添加成功 :)'}; if(!options.isSucc){ ret = {code: -1, msg: '添加失敗!'}; } setTimeout(function(){ options.callback && options.callback(ret); }, options.delay); }; var TodoActions = Reflux.createActions({ addTodo: {asyncResult: true} }); TodoActions.addTodo.listen(function(text, isSucc){ var that = this; if(typeof text !== 'string'){ that.failed({ret: 999, text: text, msg: '非法參數!'}); return; } addToServer({ text: text, isSucc: isSucc, delay: 500, callback: function(ret){ if(ret.code===0){ that.completed(ret); }else{ that.failed(ret); } } }); }); var state = { items: [], status: '' }; var TodoStore = Reflux.createStore({ init: function(){ state.items.push('睡覺'); }, listenables: [TodoActions], onAddTodo: function(text, isSucc){ var that = this; state.status = 'pending'; this.trigger(state); }, onAddTodoCompleted: function(ret){ state.status = 'success'; state.items.push(ret.text); this.trigger(state); }, onAddTodoFailed: function(ret){ state.status = 'error'; this.trigger(state); }, getState: function(){ return state; } }); TodoStore.listen(function(state){ console.log('status is: ' + state.status + ', current todos is: ' + state.items); }); // 非法參數 TodoActions.addTodo(true, true);
運行看看效果。這裏發現一個問題,儘管參數校驗不經過,但store.onAddTodo
仍是被觸發了,因而打印出了status is: pending, current todos is: 睡覺
。
而按照咱們的預期,store.onAddTodo
是不該該觸發的。
➜ examples git:(master) ✗ node 04-invalid-params.js status is: pending, current todos is: 睡覺 status is: error, current todos is: 睡覺
好在reflux裏也考慮到了這樣的場景,因而咱們能夠經過shouldEmit
來阻止store.onAddTodo
被觸發。關於這個配置參數的使用,可參考文檔。
看修改後的代碼
var Reflux = require('reflux'); /** * @param {String} options.text * @param {Boolean} options.isSucc 是否成功 * @param {Function} options.callback 異步回調 * @param {Number} options.delay 異步延遲的時間 */ var addToServer = function(options){ var ret = {code: 0, text: options.text, msg: '添加成功 :)'}; if(!options.isSucc){ ret = {code: -1, msg: '添加失敗!'}; } setTimeout(function(){ options.callback && options.callback(ret); }, options.delay); }; var TodoActions = Reflux.createActions({ addTodo: {asyncResult: true} }); TodoActions.addTodo.shouldEmit = function(text, isSucc){ if(typeof text !== 'string'){ this.failed({ret: 999, text: text, msg: '非法參數!'}); return false; } return true; }; TodoActions.addTodo.listen(function(text, isSucc){ var that = this; addToServer({ text: text, isSucc: isSucc, delay: 500, callback: function(ret){ if(ret.code===0){ that.completed(ret); }else{ that.failed(ret); } } }); }); var state = { items: [], status: '' }; var TodoStore = Reflux.createStore({ init: function(){ state.items.push('睡覺'); }, listenables: [TodoActions], onAddTodo: function(text, isSucc){ var that = this; state.status = 'pending'; this.trigger(state); }, onAddTodoCompleted: function(ret){ state.status = 'success'; state.items.push(ret.text); this.trigger(state); }, onAddTodoFailed: function(ret){ state.status = 'error'; this.trigger(state); }, getState: function(){ return state; } }); TodoStore.listen(function(state){ console.log('status is: ' + state.status + ', current todos is: ' + state.items); }); // 非法參數 TodoActions.addTodo(true, true); setTimeout(function(){ TodoActions.addTodo('起牀', true); }, 100)
再次運行看看效果。經過對比能夠看到,當shouldEmit
返回false
,就達到了以前預期的效果。
➜ examples git:(master) ✗ node 05-invalid-params-shouldEmit.js status is: error, current todos is: 睡覺 status is: pending, current todos is: 睡覺 status is: success, current todos is: 睡覺,起牀
flux的實現細節存在很多爭議,而針對文中例子,reflux的設計比較靈活,一樣是使用reflux,也能夠有多種實現方式,具體全看判斷取捨。
最後,歡迎交流。