React之flux
Flux:
簡單說,Flux 是一種架構思想,專門解決軟體的結構問題。它跟MVC 架構是同一類東西,但是更加簡單和清晰。
Flux存在多種實現(至少15種),本文采用的是Facebook官方實現。
首先,Flux將一個應用分成四個部分。
- View: 檢視層
- Action(動作):檢視層發出的訊息(比如mouseClick)
- Dispatcher(派發器):用來接收Actions、執行回撥函式
- Store(資料層):用來存放應用的狀態,一旦發生變動,就提醒Views要更新頁面
Flux 的最大特點,就是資料的”單向流動
“。
- 使用者訪問 View
- View 發出使用者的 Action
- Dispatcher 收到 Action,要求 Store 進行相應的更新
- Store 更新後,發出一個”change”事件
- View 收到”change”事件後,更新頁面
上面過程中,資料總是”單向流動”,Action >Dispatcher > Store ,任何相鄰的部分都不會發生資料的”雙向流動”。這保證了流程的清晰
。
原文及程式碼來自於阮一峰的https://github.com/ruanyf/extremely-simple-flux-demo
本文基於cra腳手架建立,es6寫法。
View(第一部分)
請開啟 Demo 的首頁index.jsx ,你會看到只加載了一個元件。
import React, { Component } from 'react'; import MyButtonController from './components/MyButtonController'; import './App.css'; class App extends Component { render() { return ( <div className="App"> <MyButtonController/> </div> ); } } export default App;
MyButtonController的原始碼如下。
import React, { Component } from 'react';
import ListStore from '../stores/ListStore';
import ButtonActions from '../actions/ButtonActions';
import MyButton from './MyButton';
class MyButtonController extends Component {
constructor(props){
super(props);
this.state = {
items: ListStore.getAll()
}
}
componentDidMount() {
ListStore.addChangeListener(this._onChange);
}
componentWillUnmount() {
ListStore.removeChangeListener(this._onChange);
}
_onChange = () => {
this.setState({
items: ListStore.getAll()
});
}
createNewItem = (event) => {
ButtonActions.addNewItem('new item');
}
render() {
return (
<MyButton
items={this.state.items}
onClick={this.createNewItem}
/>
);
}
}
export default MyButtonController;
上面程式碼中,MyButtonController將引數傳給子元件MyButton。後者的原始碼甚至更簡單。
import React, { Component } from 'react';
class MyButton extends Component {
render() {
const { items } = this.props;
const itemHtml = items.map(function (listItem, i) {
return <li key={i}>{listItem}</li>;
});
return (
<div>
<ul>{itemHtml}</ul>
<button onClick={this.props.onClick}>New Item</button>
</div>
);
}
}
export default MyButton;
上面程式碼中,你可以看到MyButton是一個純元件(即不含有任何狀態),從而方便了測試和複用。這就是”controll view”模式的最大優點。
MyButton只有一個邏輯,就是一旦使用者點選,就呼叫this.createNewItem
方法,向Dispatcher發出一個Action。
// components/MyButtonController.js
// ...
createNewItem = (event) => {
ButtonActions.addNewItem('new item');
}
上面程式碼中,呼叫createNewItem
方法,會觸發名為addNewItem
的Action。
Action
每個Action都是一個物件,包含一個actionType屬性(說明動作的型別)和一些其他屬性(用來傳遞資料)。
在這個Demo裡面,ButtonActions 物件用於存放所有的Action。
// actions/ButtonActions.js
import AppDispatcher from '../dispatcher/AppDispatcher';
const ButtonActions = {
addNewItem: function(text){
AppDispatcher.dispatch({
actionType: 'ADD_NEW_ITEM',
text: text
});
}
}
export default ButtonActions;
上面程式碼中,ButtonActions.addNewItem方法使用AppDispatcher,把動作ADD_NEW_ITEM
派發到Store。
Dispatcher
Dispatcher 的作用是將 Action 派發到 Store。你可以把它看作一個路由器,負責在 View 和 Store 之間,建立 Action 的正確傳遞路線。注意,Dispatcher 只能有一個,而且是全域性的。
Facebook官方的 Dispatcher 實現輸出一個類,你要寫一個AppDispatcher.js
,生成 Dispatcher 例項。
AppDispatcher.register()
方法用來登記各種Action的回撥函式。
import { Dispatcher } from 'flux';
import ListStore from '../stores/ListStore';
const AppDispatcher = new Dispatcher();
AppDispatcher.register(function(action){
switch(action.actionType){
case 'ADD_NEW_ITEM':
ListStore.addNewItemHandler(action.text);
ListStore.emitChange();
break;
default:
}
});
export default AppDispatcher;
上面程式碼中,Dispatcher收到ADD_NEW_ITEM
動作,就會執行回撥函式,對ListStore進行操作。
記住,Dispatcher 只用來派發 Action,不應該有其他邏輯。
Store
Store 儲存整個應用的狀態。它的角色有點像 MVC 架構之中的Model 。
在我們的 Demo 中,有一個ListStore,所有資料都存放在那裡。
// stores/ListStore.js
var objectAssign = require('object-assign');
var EventEmitter = require('events').EventEmitter;
var ListStore = objectAssign({}, EventEmitter.prototype, {
items: [],
getAll: function () {
return this.items;
},
addNewItemHandler: function (text) {
this.items.push(text);
},
emitChange: function () {
this.emit('change');
},
addChangeListener: function(callback) {
this.on('change', callback);
},
removeChangeListener: function(callback) {
this.removeListener('change', callback);
}
});
module.exports = ListStore;
上面程式碼中,ListStore.items
用來儲存條目,ListStore.getAll()
用來讀取所有條目,ListStore.emitChange()
用來發出一個”change
“事件。
由於 Store 需要在變動後向 View 傳送”change
“事件,因此它必須實現事件介面。
上面程式碼中,ListStore繼承了EventEmitter.prototype
,因此就能使用ListStore.on()
和ListStore.emit()
,來監聽和觸發事件了。
Store 更新後(this.addNewItemHandler()
)發出事件(this.emitChange()
),表明狀態已經改變。 View 監聽到這個事件,就可以查詢新的狀態,更新頁面了。
View (第二部分)
現在,我們再回過頭來看 View ,讓它監聽 Store 的 change 事件。
// components/MyButtonController.js
import React, { Component } from 'react';
import ListStore from '../stores/ListStore';
import ButtonActions from '../actions/ButtonActions';
import MyButton from './MyButton';
class MyButtonController extends Component {
constructor(props){
super(props);
this.state = {
items: ListStore.getAll()
}
}
componentDidMount() {
ListStore.addChangeListener(this._onChange);
}
componentWillUnmount() {
ListStore.removeChangeListener(this._onChange);
}
_onChange = () => {
this.setState({
items: ListStore.getAll()
});
}
createNewItem = (event) => {
ButtonActions.addNewItem('new item');
}
render() {
return (
<MyButton
items={this.state.items}
onClick={this.createNewItem}
/>
);
}
}
export default MyButtonController;
上面程式碼中,你可以看到當MyButtonController 發現 Store 發出 change 事件,就會呼叫 this._onChange 更新元件狀態,從而觸發重新渲染。
// components/MyButton.jsx
import React, { Component } from 'react';
class MyButton extends Component {
render() {
const { items } = this.props;
const itemHtml = items.map(function (listItem, i) {
return <li key={i}>{listItem}</li>;
});
return (
<div>
<ul>{itemHtml}</ul>
<button onClick={this.props.onClick}>New Item</button>
</div>
);
}
}
export default MyButton;
總結
- 資料流是單向流動的。Store中給定初始資料,提供資料的getter()方法。
- View上有互動,有資料更新就向Dispatcher發出一個Action。Action 中指定actionType和更新的資料。並通過
AppDispatcher.dispatch(action)
分發出去。並在元件渲染完成後監聽Store中資料的變化addChangeListener(),在元件銷燬時移除監聽的事件removeChangeListener(). - Dispatcher登記各種Action的回撥函式,根據不同的actionType,呼叫ListStore的方法對資料進行更新,並觸發emitChange事件。
- Store中自定義事件:addChangeListener(),removeChangeListener(),emitChange()實現事件介面。