摘要:[React]React.JS學習筆記(5) - 實作一個Flux Example
要了解任何技術,還是要需要動手做才行。上一篇文章介紹了Flux的概念,簡單的說就是讓Store與View之間的關係降低耦合度,並且讓資料流跑單向流程,避免交互影響而產生無法預期的副作用。這一篇就來透過Flux的觀念實作一個簡單Todo List的Example。
範例程式碼放這裡==>Lab-Flux-Todo
環境準備
首先,在專案目錄下建立一個Package.json檔,其內容如下:
{ | |
"name": "Lab-Flux-Todo", | |
"version": "1.0.0", | |
"description": "It's a simple lab for flux.", | |
"dependencies": { | |
"flux": "^2.0.3", | |
"object-assign": "^3.0.0", | |
"react": "^0.13.3" | |
}, | |
"devDependencies": { | |
"browserify": "^10.2.4", | |
"reactify": "^1.1.1" | |
}, | |
"scripts":{ | |
"start":"browserify -t reactify js/app.js -o ./build/bundle.js" | |
} | |
} |
接著,下npm指令進行相關套件的載入
npm install
由Package.json的定義中可以看出,我們用到的套件,除了React之外,還有Flux及object-assign。Flux套件主要是用在dispatcher上,用來建立dispatcher物件。而object-assign的功能很單純,就只是把不同的物件合併在一起,這在Store中會用到。
這個範例,是要建立一個Textbox可以輸入Todo內容,輸入完後再按下Button可以建立一個Todo Task到清單內。
先建立一個index.html頁面來放React Components
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Lab-Flux-Todo</title> | |
</head> | |
<body> | |
<div id="example"></div> | |
<script src="build/bundle.js"></script> | |
</body> | |
</html> |
再建立一個 js/app.js當作程式的進入點
var React = require("react"); | |
var TodoApp = require("./components/TodoApp.react"); | |
React.render(<TodoApp/>,document.getElementById("example")); |
View
我們先從View開始建立,之前提到Controller View是負責與Store相連結,區隔其子孫View與其他元件,以降低UI的耦合度。所以Controller View裡面除了定義會用到哪些子View之外,還會監聽(Listen) Store發生的Change事件,也就是定義相對應的處理函式。其處理方式,就是在React的生命週期function(componentDidMount, componentWillUnmount)中加入及移除處理function ->todoChangeHandler()。
以下是js/components/TodoApp.react.js
var React = require("react"); | |
var TodoAppList = require("./TodoAppList.react"); | |
var TodoStore = require("../stores/TodoStore"); | |
var TodoAction = require("../actions/TodoAction"); | |
var TodoApp = React.createClass({ | |
getInitialState: function(){ | |
return{todoItems:TodoStore.getTodoItems()}; | |
}, | |
componentDidMount: function(){ | |
TodoStore.addChangeListener(this.changeHandler); | |
}, | |
componentWillUnmount: function(){ | |
TodoStore.removeChangerListener(this.changeHandler); | |
}, | |
changeHandler: function(){ | |
this.setState({todoItems:TodoStore.getTodoItems()}); | |
}, | |
handleAddTodo: function(){ | |
var newTodo = React.findDOMNode(this.refs.txtTodo).value.trim(); | |
TodoAction.createTodo(newTodo); | |
React.findDOMNode(this.refs.txtTodo).value = ''; | |
}, | |
render: function(){ | |
return( | |
<div> | |
<h1>Todo</h1> | |
<input type='text' ref='txtTodo'/> | |
<button onClick={this.handleAddTodo}>Add</button> | |
<TodoAppList Items={this.state.todoItems}/> | |
</div> | |
); | |
} | |
}); | |
module.exports = TodoApp; |
TodoApp.react.js監聽(Listen) Store發生的Change事件,也會取得Store中的資料。取得資料後,就會把資料透過props傳給其子孫 Component,其子孫Component再依據傳進來的資料做顯示的處理。概念上,這些子孫Component最好是Stateless的,也就是說裡面的邏輯不要太複雜,只要管好怎麼依據資料顯示內容就夠了。就這個範例中,TodoAppList.react.js只要負責顯示現有的Todo清單就好。
以下是js/components/TodoAppList.react.js
var React = require("react"); | |
var Todolist = React.createClass({ | |
render: function(){ | |
function itemElement(inTodoText,index){ | |
return <li key={index}>{inTodoText}</li> | |
}; | |
return(<ul>{this.props.Items.map(itemElement)}</ul>); | |
} | |
}); | |
module.exports = Todolist; |
Action
這裡的Action很簡單,因為我們這個簡單的範例只有一個動作,就是建立一個Todo Task。所以可以看到Code中的TodoAction物件,只有一個createTodo這個function而已。
以下是js/actions/TodoAction.js
var AppDispatcher = require("../dispatcher/AppDispatcher"); | |
var TodoAction = { | |
createTodo: function(inTodoText){ | |
AppDispatcher.dispatch({ | |
actionType: "CreateTodo", | |
text: inTodoText | |
}); | |
} | |
}; | |
module.exports = TodoAction; |
Dispatcher
Dispatcher裡面也沒有甚麼Code,就只是把Dispatcher物件建立起來而已。
以下是js/dispatcher/AppDispatcher.js
var Dispatcher = require('flux').Dispatcher; | |
module.exports = new Dispatcher(); |
Store
另一個較為複雜的,就是Store了。Store做的事情主要有兩個,一個是處理不同的Action的邏輯,另一個是處理資料變動後,要觸發事件(Event)讓View可以知道。
以下是js/stores/TodoStore.js
var AppDispatcher = require("../dispatcher/AppDispatcher"); | |
var EventEmitter = require("events").EventEmitter; | |
var assign = require("object-assign"); | |
var CHANGE_EVENT = 'change'; | |
var todoItems = ["Do something"]; | |
function createTodo(inTodoText){ | |
todoItems = todoItems.concat(inTodoText); | |
} | |
var TodoStore = assign({},EventEmitter.prototype,{ | |
getTodoItems: function(){ | |
return todoItems; | |
}, | |
emitChange: function(){ | |
this.emit(CHANGE_EVENT); | |
}, | |
addChangeListener: function(callback){ | |
this.on(CHANGE_EVENT,callback); | |
}, | |
removeChangeListener: function(callback){ | |
this.removeListener(CHANGE_EVENT,callback); | |
} | |
}); | |
AppDispatcher.register(function(action){ | |
switch(action.actionType){ | |
case "CreateTodo": | |
createTodo(action.text); | |
TodoStore.emitChange(); | |
break; | |
default: | |
} | |
}); | |
module.exports = TodoStore; |
Action的處理,主要是透過Dispatcher的register()進行。可以看到程式碼中的這個部分,是注入一個處理不同actoinType的邏輯到Dispatcher物件中。所以當View那端發生不同的Action時,Dispatcher就可以執行相對應的處理了。
另一件要做的事情,就是進行事件(event)的發布。在這個範例中,唯一的事件就是todo的清單增加了新的todo task。這裡使用了EventEmitter,EventEmitter是Node.js中的一個事件處理的物件。用它來協助進行事件的建立,以及加入移除事件的Listener。
就在這個宣告中,透過object-assign將EventEmitter的function整合進來到TodoStore物件中。並且使用EventEmitter的emit()以進行事件的觸發,使用EventEmitter的on()以加上事件的處理函式,使用EventEmitter的removeListener()以移除事件的處理函式。
最好還是把整式碼整個看過一遍,了解其流程運作及相互關係。所以再放一次範例程式碼的位置==>Lab-Flux-Todo