既然已經可以使用TypeScript寫React了-[TypeScript]使用TypeScript寫React.JS,用TypeScript寫React的Flux應該不會是太大的問題,所以又繼續的寫了一個Flux的Lab。這個Lab延續使用[React]React.JS學習筆記(5) - 實作一個Flux Example的Example,只是語法換成TypeScript而已。
[TypeScript]使用TypeScript寫Flux
既然已經可以使用TypeScript寫React了-[TypeScript]使用TypeScript寫React.JS,用TypeScript寫React的Flux應該不會是太大的問題,所以又繼續的寫了一個Flux的Lab。這個Lab延續使用[React]React.JS學習筆記(5) - 實作一個Flux Example的Example,只是語法換成TypeScript而已。
Dispatcher & Constants
Dispatcher的程式很簡單,只是把Dispatcher物件建立起來而已。
dispatcher/AppDispatcher.tsx
import * as Flux from 'flux';
var Dispatcher = new Flux.Dispatcher();
export default Dispatcher;
而Constants就有使用到TypeScript的特性 - Enum,以取代傳統使用字串物件的方式。
constants/ActionConstants.tsx
enum ActionConstants {
TODO_CREATE,
}
export default ActionConstants;
Action
因為Constants使用Enum,所以可以看到,當Action在進行dispatch
的時候,ActionType就不是使用字串HardCode的方式了。
actions/TodoAction.tsx
import AppDispatcher from '../dispatcher/AppDispatcher';
import ActionConstants from '../constants/ActionConstants';
class TodoAction {
createTodo(inTodoText) {
AppDispatcher.dispatch({
actionType: ActionConstants.TODO_CREATE,
text: inTodoText
});
}
}
export default TodoAction;
Store
接下來就是Flux中比較核心的的Store,Store做的事情主要有兩個,一個是處理不同的Action的邏輯,另一個是處理資料變動後,要觸發事件(Event)讓View可以知道。
透過Dispatcher處理Action的邏輯
Constants的Enum型別在這裡就發揮得很好,在switch語句中就看不到字串HardCode的情況,直接用點語法帶出- ActionConstants.TODO_CREATE
。
var TodoStoreObj: TodoStore = new TodoStore();
AppDispatcher.register(function (action: ITodoAction): void {
var text: string;
switch (action.actionType) {
case ActionConstants.TODO_CREATE:
createTodo(action.text);
TodoStoreObj.emitChange();
break;
default:
break;
}
});
Store的Event
之前是使用object-assign的方式將EventEmitter的function與我們自己寫的Store放在一起。使用TypeScript來寫,就可以透過繼承的方式做到相同的效果,這樣的寫法所傳達出的語意也比較清楚直接。
class TodoStore extends EventEmitter {
getTodoItems() {
return todoItems;
}
emitChange() {
this.emit(CHANGE_EVENT);
}
addChangeListener(inCallback) {
this.on(CHANGE_EVENT, inCallback);
}
removeChangeListener(inCacllback) {
this.removeListener(CHANGE_EVENT, inCacllback);
}
}
View
前面的苦工做完後,View就簡單了,但我還是踩了一些坑。主要有兩個,一個是refs的存取,要使用以下的語法。因為我使用的React是V0.14.7的版本,所以findDOMNode原本由React換到ReactDOM了:
ReactDOM.findDOMNode<HTMLInputElement>(this.refs["txtT"]).value
另外一個是在HTML元件的事件綁定到function時,需要使用bind()
將this
傳進,才有辦法正確取得React的refs, state, props物件。
<button onClick={this.handleAddTodo.bind(this)}>Add</button>
components/TodoApp.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import TodoStore from '../stores/TodoStore';
import TodoAction from '../actions/TodoAction';
import TodoAppList from './TodoAppList';
export default class TodoApp extends React.Component<{}, {}> {
//********** React Component LifeCycle **********
constructor(props) {
super(props);
this.state = { todoItems: TodoStore.getTodoItems() };
}
componentDidMount() {
TodoStore.addChangeListener(this.changeHandler.bind(this));
}
componentWillUnmount() {
TodoStore.removeChangeListener(this.changeHandler.bind(this));
}
//********** Features **********
changeHandler() {
this.setState({ todoItems: TodoStore.getTodoItems() });
}
public handleAddTodo() {
var newTodo = ReactDOM.findDOMNode<HTMLInputElement>(this.refs["txtT"]).value;
new TodoAction().createTodo(newTodo);
ReactDOM.findDOMNode<HTMLInputElement>(this.refs["txtT"]).value = '';
}
//********** DOM **********
render() {
return (
<div>
<h1>Todo</h1>
<input type='text' ref="txtT"></input>
<button onClick={this.handleAddTodo.bind(this)}>Add</button>
<TodoAppList Items={TodoStore.getTodoItems()}/>
</div>
);
}
}
環境設定
完整的Example Code放在GitHub上,下載下來後,依據底下的步驟進行套件的安裝及TypeScript的編譯。
Package Installation
安裝tsd後,執行以下的Command下載TypeScript定義檔
tsd install
安裝npm後,執行以下的Command進行package的安裝
npm install
Build script
透過編輯器進行編譯TypeScript,或是執行以下Command進行TypeScript編譯
tsc
有使用browserify,故執行以下的npm 命令打包js
npm start
Demo
執行npm start後會產生bundle.js,這時直接開啟index.html就可以看到效果