標籤:
這個是 Facebook 官方學習 Flux 的 todo 例子
想用這個例子來總結一下怎麼從零開始用 React 和 Flux 構建一個 App
Structure
App├─ javascripts│ ├─ actions│ │ ├─ TodoActions.js│ ├─ components│ │ ├─ TodoComponents│ │ │ ├─ TodoApp.js│ │ │ ├─ Header.js│ │ │ ├─ MainSection.js│ │ │ ├─ Footer.js│ │ │ ├─ TodoItem.js│ │ │ ├─ TodoTextInput.js│ ├─ constants│ │ ├─ TodoConstants.js│ ├─ dispatcher│ │ ├─ AppDispatcher.js│ ├─ stores│ │ ├─ TodoStore.js├─ stylesheets│ ├─ TodoStyle.css├─ index.html├─ README.md├─ package.json├─ webpack.config.js
可能你看到的這個結構和官方 demo 的結構會有點不同,那是因為官方的 demo 整個的本身只有 todo 這個功能,但實際上遠遠不至。所以在 components 下會細分是什麼部分的組件,像 TodoComponents
關於 Flux 裡的 Action, Dispatcher, Store and Controller View 這些概念如果還不瞭解的話可以去看看這兩篇文章
- Flux For Stupid People38
- Getting To Know Flux, the React.js Architecture25
Components
首先你通過你 app 的介面來確定你的組件,如
從這個圖我們可以看到,我們的組件有
- Header
- MainSection
- Footer
- TodoItem
- TodoTextInput
在 MainSection 裡有 TodoTextInput 是當我們雙擊我們已經存在的 todo,可以對其進行更新
Actions
確定了組件之後,我們就可以確定我們的 TodoActions 檔案了。
對於這個 Todo app,有多少 actions 呢?
- create - 我們可以建立一條新的 todo
- updateText - 雙擊已經存在的 todo,可以對其進行更新
- toggleComplete - 看到每一條前面的勾勾了嗎?就是可以給你決定是否完成了
- toggleCompleteAll - 看到輸入框前面那個 ? 了嗎?就是讓你全部完成,或者全部不完成的
- destroy - 看到每一條後面那個叉叉了嗎?要 hover 在上面才看到的,就是給你刪除這一條的
- destroyCompleted - 看到 Footer 下面那個 Clear completed 了嗎?就是給我們刪除已經完成的 todo 的
就是這樣,我們根據我們的需求在這個檔案裡定義不同的 action 函數,但這裡的函數並不涉及邏輯的處理,這裡函數只是告訴我們的 Dispatcher,使用者進行了什麼操作。所以我們只需要給 Dispatcher 傳的一個對象,對象裡一個必要的屬性就是 actionType。如果使用者進行這個操作有給我們傳的參數的話。那參數也會放在這個對象裡。
例如使用者想建立一條新的 todo ,就是我們的 create action 了
import AppDispatcher from ‘../dispatcher/AppDispatcher‘; import TodoConstants from ‘../constants/TodoConstants‘; var TodoActions = { create (text) { AppDispatcher.dispatch({ actionType: TodoConstants.TODO_CREATE, text: text }); }, // other actions } export default TodoActions;
當我們執行 AppDispatcher.dispatch 這個方法,並傳給他一個有 actionType 屬性的對象時,他就會在大喊,“有人做了一個操作呀,這個操作就是 xxx (actionType 的值),還帶了個參數,你們哪個來處理一下呀”
嗯嗯,就是這樣,資料就從 Action 傳到了 Dispatcher
Dispatcher
Dispatcher 的具體實現可以看 github.com/facebook/flux/blob/master/src/Dispatcher.js
遊客只可以貼兩個連結,我也是醉
當我們用 Facebook 給我們提供的 Dispatcher,那麼一切都會變得簡單了許多
npm install --save flux
import Flux from ‘flux‘; var Dispatcher = Flux.Dispatcher; export default new Dispatcher();
Dispatcher 在整個應用
只有一個,只有一個,只有一個
有人就說了,你 Dispatcher 只負責喊的,我不要你也好像可以呀。嗯嗯,那就不叫 Fulx 了,叫 Reflux github.com/spoike/refluxjs
Constants
剛剛我們看到在我們的 Actions 裡,actionType: TodoConstants.TODO_CREATE,這個 TodoConstants 其實就是我們操作的名字,相當於一個常量,定義在 Constants 裡方便管理和調用而已。
一般你有多少個 action,就有多少個常量在這個 Constants 裡
KeyMirror 就是建立一個對象,裡面鍵的值等於鍵的名字 - -
Stores
主角登場! 但, Store 是什嗎?
Store 是一個儲存資料的地方
var _todo = {};
Store 是一個充滿邏輯的地方
所有 actions 的邏輯處理,都會在這裡發生。像我們的 create action
function create (text) { var id = (new Date() + Math.floor(Math.random() * 999999)).toString(36); _todos[id] = { id: id, complete: false, text: text };}
Store 是一個響應 Dispatcher 呼喊的地方
“有人做了一個操作呀,這個操作就是 xxx (actionType 的值),還帶了個參數,你們哪個來處理一下呀”
在 Store 裡,我們通過 Dispatcher “註冊”了一個回呼函數,每當我們調用 dispatch 函數的時候,就是 Dispatcher 大喊的時候,我們根據不同的 actionType,來調用我們不同的邏輯處理函數,像這樣
import AppDispatcher from ‘../dispatcher/AppDispatcher‘; import TodoConstants from ‘../constants/TodoConstants‘;AppDispatcher.register((action) => { var text; switch(action.actionType) { case TodoConstants.TODO_CREATE: text = action.text.trim(); if (text !== ‘‘) { create(text); TodoStore.emitChange(); } break; // other case }});
Store 是一個鞭策 Controller View 改變的地方
每當 Store 改變了資料之後,他都要 Controller View 跟著他改變。他們還約定了暗號
var CHANGE_EVENT = ‘change‘;
Store 跟 Controller View 說,我一喊 “變”,你聽到之後,你就叫你的手下一起變。
Controller View 說好。
但是 Store 不會喊,Controller View 也聽不到。
所以 Store 從 EventEmitter中學會了喊,也給 Controller View 買來了助聽器
import assign from ‘object-assign‘; var EventEmitter = require(‘events‘).EventEmitter; var TodoStore = assign({}, EventEmitter.prototype, { areAllComplete () { for (var id in _todos) { if (!_todos[id].complete) { return false; } } return true; }, getAll () { return _todos; }, emitChange () { this.emit(CHANGE_EVENT); }, addChangeListener (callback) { this.on(CHANGE_EVENT, callback); }, removeChangeListener (callback) { this.removeListener(CHANGE_EVENT, callback); }}); export default TodoStore;
所以每當執行完邏輯處理函數之後,Store 都會喊一句 TodoStore.emitChange();
助聽器 addChangeListener (callback) { this.on(CHANGE_EVENT, callback) } 也買好了,成不成功,就看 Controller View 了
Controller View
在 Components 裡,你看不到 TodoApp 這個組件,因為對於 Todo 這個 App,TodoApp 這個組件,就是 Contriller View,他掌管全部的 Components。
但是重要的是,他怎麼帶 Store 給他買的助聽器
componentDidMount () { TodoStore.addChangeListener(this._onChange.bind(this));}
當組件渲染完成後,就綁定了 Store 的 addChangeListener,並回調了自己的 onChange 方法。
_onChange () { this.setState(this.getTodoState.bind(this)());}
Store 一喊,Controller View 聽到之後,更新所有資料,以 props 的方式傳給他的手下 - 即他掌管的 Components
Summary
現在我們來疏理一下整個流程(就 create 而言)
- 使用者輸入要新增的 todo,一敲斷行符號,觸發 onKeydown 方法。
- onKeydown 調用了 onSave 方法,onSave 調用了 TodoActions.create 方法。
- TodoActions.create 觸發了 AppDispatcher.dispatch 方法,AppDispatcher 大喊了一聲。
- TodoStore 響應,根據 actionType 調用了 create 邏輯處理函數,執行完,喊了一句 “變”。
- Controller View 帶著助聽器聽到了接著更新資料,把資料傳給了各個 Components。
- 重新渲染,新增完畢。
以上是本人淺顯的理解,如有錯誤,歡迎指正 : )
【天贏金創】React flux九淺一深