[React]React.JS學習筆記(5) - 實作一個Flux Example

摘要:[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"
}
}
view raw package.json hosted with ❤ by GitHub

接著,下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>
view raw index.html hosted with ❤ by GitHub

 

再建立一個 js/app.js當作程式的進入點

var React = require("react");
var TodoApp = require("./components/TodoApp.react");
React.render(<TodoApp/>,document.getElementById("example"));
view raw app.js hosted with ❤ by GitHub

 

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;
view raw TodoAction.js hosted with ❤ by GitHub

 

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;
view raw TodoStore.js hosted with ❤ by GitHub

 

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