2015年3月23日 星期一

React.js - react/flux 範例

本篇教學範例的完整原始碼在 >>這裡<<

如果對 Flux 的架構和細節有不清楚的,網路資源很多可以查,建議看英文比較好

我之前有寫一篇關於 Flux 的架構分想也可以參考 >>Facebook/Flux 架構分享<<

這邊在用最簡單的方式介紹一下 Flux 的架構

原則上 Flux 是一個資料流只有單一方向的設計原則,並且把原件區分為 View, Action, Store, Dispatcher

以下用一張簡單的圖來說明
























第一張圖是 Flux 的架構圖,我有簡化過,因為我是認為 Flux 的原圖會讓人誤會

第二張圖是實際的運行中的資料流



接下來用一個很簡單的範例來實作 react 與 flux 的範例程式來介紹 flux

首先在專案目錄(react-flux-demo)下建立 package.json,內容如下即可

{
"name": "react-flux-demo",
"version": "1.0.0",
"description": "It's a simple demo for react and flux."
}
view raw package.json hosted with ❤ by GitHub
接著執行以下指令
$ npm install react --save
$ npm install flux --save
$ npm install object-assign --save #object-assign > https://github.com/sindresorhus/object-assign
view raw shell1.sh hosted with ❤ by GitHub
上面我們透過 npm 安裝了 react, flux 以及 object-assign

其中 object-assign 是類似用來拷貝物件的一個 node module

接下來我們就從下面的 HTML 開始撰寫 react/flux 的程式

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>React demo with flux</title>
</head>
<body>
<div id="example"></div>
</body>
</html>
view raw index.html hosted with ❤ by GitHub
這次的範例很簡單,頁面上只有兩個 button ,分別代表遞增與遞減的功能,下方則會顯示目前的數值

先從程式入口點開始 js/app.js

var React = require("react");
var DemoApp = require("./components/DemoApp.react");
React.render(
<DemoApp />,
document.getElementById("example")
);
view raw app.js hosted with ❤ by GitHub
接下來是 js/components/DemoApp.react.js,這個是主頁面的 View

var React = require("react");
var Header = require("./Header.react");
var Content = require("./Content.react");
var DemoStore = require("../stores/DemoStore");
var DemoApp = React.createClass({
//initial stage, use Store to get default value and bind to the React state
getInitialState: function(){
return{
value: DemoStore.getValue()
};
},
componentDidMount: function() {
//listen up an event from Store, if value in Store changed, _onChange function will be triggered
DemoStore.addChangeListener(this._onChange);
},
componentWillUnmount: function() {
DemoStore.removeChangeListener(this._onChange);
},
render: function(){
return(
<div>
<Header />
<Content value={this.state.value}/>
</div>
);
},
// If value in Store change, this function will be triggered
// so we need to reset the state
_onChange: function(){
this.setState({
value: DemoStore.getValue()
});
}
});
module.exports = DemoApp;
再來則是剩下的兩個 View:js/components/Header.react.js 以及 js/components/Content.react.js

Header 負責呈現兩個 button 並負責 click 的事件以及與 Action 溝通

Content 單純透過 React 的 State 來呈現目前的數值是多少

var React = require("react");
var DemoAction = require("../actions/DemoAction");
var Header = React.createClass({
//handle click Increase button, call DemoAction's increase function
handleIncrease: function(){
DemoAction.increase();
},
//handle click Decrease button, call DemoAction's decrease function
handleDecrease: function(){
DemoAction.decrease();
},
render: function(){
return(
<div>
<section>
<button onClick={this.handleIncrease}>Increase</button>
<button onClick={this.handleDecrease}>Decrease</button>
</section>
</div>
);
}
});
module.exports = Header;
view raw Header.react.js hosted with ❤ by GitHub
var React = require("react");
var Content = React.createClass({
render: function(){
return(
<div>{this.props.value}</div>
)
}
});
module.exports = Content;

接下來就是 Action 的部分,這是相對簡單的地方,主要就是負責提供 View 處理 user 行為的 function

並且透過 Dispatcher 來發送這個事件出去,而只有有興趣的 Store 會聆聽這些事件

所以先來看一下 js/actions/DemoAction.js

var DemoDispatcher = require("../dispatcher/DemoDispatcher");
var DemoAction = {
increase: function(){
DemoDispatcher.dispatch({
actionType: "Increase"
});
},
decrease: function(){
DemoDispatcher.dispatch({
actionType: "Decrease"
});
}
};
module.exports = DemoAction;
view raw DemoAction.js hosted with ❤ by GitHub
再來是 Dispatcher,dispatcher 在 flux 的架構中被定義為一個 singleton 的元件,只負責做事件的派送

由於大部分 dispatcher 的處理邏輯 flux 已經 cover 掉了,所以你的 dispatcher 會相當精簡

以下是 js/dispatcher/DemoDispatcher.js 的程式

var Dispatcher = require('flux').Dispatcher;
module.exports = new Dispatcher();
最後就是最複雜的部分了:js/stores/DemoStore.js (我把一些解釋寫在註解內了)

var DemoDispatcher = require('../dispatcher/DemoDispatcher');
var EventEmitter = require('events').EventEmitter;
var assign = require("object-assign");
var CHANGE_EVENT = 'change';
// Our value show on the view
var _value = 0;
// increase logic
function _increase(){
_value++;
}
// decrease logic
function _decrease(){
_value--;
}
// define a Store object the extends EventEmitter from node.js event lib
var DemoStore = assign({}, EventEmitter.prototype, {
getValue: function(){
return _value;
},
// trigger a value changed event!!
emitChange: function() {
this.emit(CHANGE_EVENT);
},
// the callback function will be defined and be passed from view
// in our example, the callback function be defined in DemoApp.react.js and named _onChange
addChangeListener: function(callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener: function(callback) {
this.removeListener(CHANGE_EVENT, callback);
}
});
//Use dispatcher to listen some events
DemoDispatcher.register(function(action){
switch(action.actionType){
case "Increase":
_increase(); // do increase logic
DemoStore.emitChange(); // after value change, trigger a event
break;
case "Decrease":
_decrease(); // do decrease logic
DemoStore.emitChange(); // after value change, trigger a event
break;
default:
}
});
module.exports = DemoStore;
view raw DemoStore.js hosted with ❤ by GitHub
完成之後,我們就要利用 Browserify 來做簡單的 build 動作

為什麼要用 Browserify?其實在前面的所有範例程式中,前面幾行都是用 require 這個函式來引入其他的 JS 檔

或是引入 react 以及 flux 等等, require 這個函式不是憑空出現的

畢竟我們又沒有額外引入 RequireJS 或 CommonJS 之類的,這是因為有 Browserify 的幫忙

有空可以看看 >>Browserify<< ,以下是他最簡短也最明瞭的解釋

Browserify lets you require('modules') in the browser by bundling up all of your dependencies.

簡單說你要透過 Browserify 來 build 你目前的所有程式,否則瀏覽器會看不懂 require ~

另外你還需要 Reactify,這是一個 Browserify 的 transform

能夠讓 build 的過程中也能夠轉換 react 的 JSX 語法,否則你會 build 不成功的

所以要透過以下指令來安裝 Browserify 以及 Reactify

$ npm install browserify -g
$ npm install browserify --save-dev
$ npm install reactify --save-dev
view raw install.sh hosted with ❤ by GitHub
並且修改 package.json,加入Browserify 的 transform

"browserify": {
"transform": [
"reactify"
]
}
view raw package1.json hosted with ❤ by GitHub
最後就可以執行以下的指令來進行 building 的動作

browserify -t reactify js/*.js -o ./build/bundle.js

這個指令的結果會在專案目錄下的 build 目錄內產生一個 bundle.js

最後一步我們就可以將這個 bundle.js 加到原先的 index.html 內了

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>React demo with flux</title>
</head>
<body>
<div id="example"></div>
<script src="build/bundle.js"></script>
</body>
</html>
view raw index1.html hosted with ❤ by GitHub
==================================================

最後補充,如果想用 gulp 執行 build 的動作的話

可以參考 github 上的 Gulpfile.js 以及 package.json 的配置

再次附上此次範例的連結 >>點我<<

沒有留言:

張貼留言