Flux 学习笔记

大部分和 Flux 有关的东西都能看到这么一个图,讲述 Flux “单向数据流”的概念。

Flux

相同的,Geek 一点的有下面这个:

1
2
3
4
Views ---> (actions) ---> Dispatcher --> (registered callback) --> Stores -----+
Ʌ |
| V
+- (Controller-Views "change" event handlers) - (Stores emit "change" events) -+

官网说 Flux 有 3 个主要的组成部分:dispatcherstoresviews。同时,我觉得 action 也是一个非常重要的概念。

注:文中代码大部分截取自 flux-todomvc

views

view 很好理解,就是一大堆的 React 组件,在标准代码结构中表现为 components/*.react.js。在 view 创建时会向 stores 绑定 'change' 事件,当其在销毁之前取消绑定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var TodoApp = React.createClass({

// ...

componentDidMount: function() {
TodoStore.addChangeListener(this._onChange);
},

componentWillUnmount: function() {
TodoStore.removeChangeListener(this._onChange);
},

render: function() {
// ...
},

/**
* Event handler for 'change' events coming from the TodoStore
*/
_onChange: function() {
this.setState(getTodoState());
}
});

另一方面,view 会产生 action,传递给 dispatcher

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var Header = React.createClass({
/**
* @return {object}
*/
render: function() {
return (
<header id="header">
<h1>todos</h1>
<TodoTextInput
id="new-todo"
placeholder="What needs to be done?"
onSave={this._onSave}
/>
</header>
);
},

/**
* Event handler called within TodoTextInput.
* Defining this here allows TodoTextInput to be used in multiple places
* in different ways.
* @param {string} text
*/
_onSave: function(text) {
if (text.trim()){
TodoActions.create(text);
}
}
});

dispatcher

dispatcher 传递来自 view 或者服务器的 action,检查其中是否有对应的注册的回调函数(registered callback),这样就能影响到 stores

因为 dispatcher 只是机械地传递,而注册动作是在 stores 里面进行的,因此大多数情况下一个应用里面只要一个 AppDispatcher.js 就够了,而且都长一个样!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
'use strict';

var Dispatcher = require('flux').Dispatcher;
var assign = require('object-assign');

var AppDispatcher = assign(new Dispatcher(), {
/**
* @param {object} action The details of the action, including the action's
* type and additional data coming from the server.
*/
handleServerAction: function (action) {
this.dispatch({
source: 'SERVER_ACTION',
action: action
});
},

/**
* A bridge function between the views and the dispatcher, marking the action
* as a view action. Another variant here could be handleServerAction.
* @param {object} action The data coming from the view.
*/
handleViewAction: function(action) {
this.dispatch({
source: 'VIEW_ACTION',
action: action
});
}
});

module.exports = AppDispatcher;

stores

感觉和一般 MVC 里面的 model 比较类似,官网说:

Their role is somewhat similar to a model in a traditional MVC, but they manage the state of many objects — they are not instances of one object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var TodoStore = assign({}, EventEmitter.prototype, {

// ...

emitChange: function() {
this.emit(CHANGE_EVENT);
},

addChangeListener: function(callback) {
this.on(CHANGE_EVENT, callback);
},

removeChangeListener: function(callback) {
this.removeListener(CHANGE_EVENT, callback);
}
});

stores 里,将 action 注册到 dispatcher,其中的回调方法直接影响其中的数据模型,而当这些数据变化的时候就会触发 'change' 事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
AppDispatcher.register(function(payload) {
var action = payload.action;

switch(action.actionType) {
case 'ACTION_NAME':
// ...
default:
return true;
}

TodoStore.emitChange();
return true; // No errors. Needed by promise in Dispatcher.
});

action

我想上面已经讲清楚了,view 或者服务器产生 action,传递给 dispatcher

1
2
3
4
5
6
7
8
9
10
11
12
13

var TodoActions = {

create: function(text) {
AppDispatcher.handleViewAction({
actionType: 'ACTION_NAME',
text: text
});
},

// ...

};