├── .gitignore
├── README.md
├── actions
└── ButtonActions.js
├── components
├── MyButton.jsx
└── MyButtonController.jsx
├── dispatcher
└── AppDispatcher.js
├── img
├── banner.png
├── dataflow.png
├── screenshot.png
└── screenshot1.png
├── index.html
├── index.jsx
├── package.json
├── stores
└── ListStore.js
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This demo helps you learn [Flux architecture](https://facebook.github.io/flux/). It is inspired by Andrew Ray's great article [Flux For Stupid People](http://blog.andrewray.me/flux-for-stupid-people/).
2 |
3 | ## What is Flux?
4 |
5 | Flux, invented by Facebook, is an architecture pattern for building client-side web applications.
6 |
7 | It is similar to MVC architecture, but Flux's concept is [much clearer](http://www.infoq.com/news/2014/05/facebook-mvc-flux) than MVC's, and easier to learn.
8 |
9 | 
10 |
11 | ## How to Play?
12 |
13 | Install the demo.
14 |
15 | ```bash
16 | $ git clone git@github.com:ruanyf/extremely-simple-flux-demo.git
17 | $ cd extremely-simple-flux-demo && npm install
18 | $ npm start
19 | ```
20 |
21 | Visit http://127.0.0.1:8080 with your browser.
22 |
23 | 
24 |
25 | You should see a button. Click it. That's all.
26 |
27 | ## Core Concepts
28 |
29 | According to Flux, an application should be divided into four parts.
30 |
31 | > - **Views**: the UI layer
32 | > - **Actions**: messages sent from Views (e.g. mouseClick)
33 | > - **Dispatcher**: a place receiving actions, and calling callbacks
34 | > - **Stores**: a place managing the Application's state, and reminding Views to update
35 |
36 | 
37 |
38 | The key feature of the Flux architecture is "one way" (unidirectional) data flow.
39 |
40 | > 1. User interacts with Views
41 | > 1. Views propagate an Action triggered by user
42 | > 1. Dispatcher receives the Action and updates the Store
43 | > 1. Store emits a "change" event
44 | > 1. Views respond to the "change" event and update itself
45 |
46 | Don't get it? Take it easy. I will give you the details soon.
47 |
48 | ## Demo Details
49 |
50 | Now let us follow the demo to learn Flux.
51 |
52 | First of all, Flux is usually used with React. So your familiarity with React is assumed. If not, I prepared a [React tutorial](https://github.com/ruanyf/react-demos) for you.
53 |
54 | ### Views
55 |
56 | Our demo application's [`index.jsx`](https://github.com/ruanyf/extremely-simple-flux-demo/blob/master/index.jsx) has only one component.
57 |
58 | ```javascript
59 | // index.jsx
60 | var React = require('react');
61 | var ReactDOM = require('react-dom');
62 | var MyButtonController = require('./components/MyButtonController');
63 |
64 | ReactDOM.render(
65 | ,
66 | document.querySelector('#example')
67 | );
68 | ```
69 |
70 | In the above code, you might notice our component's name isn't `MyButton`, but `MyButtonController`. Why?
71 |
72 | Because I use React's [controller view pattern](http://blog.andrewray.me/the-reactjs-controller-view-pattern/) here. A controller view component holds all states, then passes this data to its descendants. `MyButtonController`'s [source code](https://github.com/ruanyf/extremely-simple-flux-demo/blob/master/components/MyButtonController.jsx) is simple.
73 |
74 | ```javascript
75 | // components/MyButtonController.jsx
76 | var React = require('react');
77 | var ButtonActions = require('../actions/ButtonActions');
78 | var MyButton = require('./MyButton');
79 |
80 | var MyButtonController = React.createClass({
81 | createNewItem: function (event) {
82 | ButtonActions.addNewItem('new item');
83 | },
84 |
85 | render: function() {
86 | return ;
89 | }
90 | });
91 |
92 | module.exports = MyButtonController;
93 | ```
94 |
95 | In the above code, `MyButtonController` puts its data into UI component `MyButton`'s properties. `MyButton`'s [source code](https://github.com/ruanyf/extremely-simple-flux-demo/blob/master/components/MyButton.jsx) is even simpler.
96 |
97 | ```javascript
98 | // components/MyButton.jsx
99 | var React = require('react');
100 |
101 | var MyButton = function(props) {
102 | return
103 |
104 |
;
105 | };
106 |
107 | module.exports = MyButton;
108 | ```
109 |
110 | In the above code, you may find [`MyButton`](https://github.com/ruanyf/extremely-simple-flux-demo/blob/master/components/MyButton.jsx) is a pure component (meaning stateless), which is really the biggest advantage of the controll view pattern.
111 |
112 | Here, the logic of our application is when user clicks `MyButton`, the [`this.createNewItem`](https://github.com/ruanyf/extremely-simple-flux-demo/blob/master/components/MyButtonController.jsx#L27) method will be called. It sends an action to Dispatcher.
113 |
114 | ```javascript
115 | // components/MyButtonController.jsx
116 |
117 | // ...
118 | createNewItem: function (event) {
119 | ButtonActions.addNewItem('new item');
120 | }
121 | ```
122 |
123 | In the above code, calling the `createNewItem` method will trigger an `addNewItem` action.
124 |
125 | ### What is an Action?
126 |
127 | An action is an object which has some properties to carry data and an `actionType` property to identify the action type.
128 |
129 | In our demo, the [`ButtonActions`](https://github.com/ruanyf/extremely-simple-flux-demo/blob/master/actions/ButtonActions.js) object is the place we hold all actions.
130 |
131 | ```javascript
132 | // actions/ButtonActions.js
133 | var AppDispatcher = require('../dispatcher/AppDispatcher');
134 |
135 | var ButtonActions = {
136 | addNewItem: function (text) {
137 | AppDispatcher.dispatch({
138 | actionType: 'ADD_NEW_ITEM',
139 | text: text
140 | });
141 | },
142 | };
143 | ```
144 |
145 | In the above code, the `ButtonActions.addNewItem` method will use `AppDispatcher` to dispatch the `ADD_NEW_ITEM` action to the Stores.
146 |
147 | ### Dispatcher
148 |
149 | The Dispatcher transfers the Actions to the Stores. It is essentially an event hub for your application's Views. There is only one global Dispatcher.
150 |
151 | We use the [Facebook official Dispatcher Library](https://github.com/facebook/flux), and write a [`AppDispatcher.js`](https://github.com/ruanyf/extremely-simple-flux-demo/blob/master/dispatcher/AppDispatcher.js) as our application's dispatcher instance.
152 |
153 | ```javascript
154 | // dispatcher/AppDispatcher.js
155 | var Dispatcher = require('flux').Dispatcher;
156 | module.exports = new Dispatcher();
157 | ```
158 |
159 | `AppDispatcher.register()` is used for registering a callback for actions.
160 |
161 | ```javascript
162 | // dispatcher/AppDispatcher.js
163 | var ListStore = require('../stores/ListStore');
164 |
165 | AppDispatcher.register(function (action) {
166 | switch(action.actionType) {
167 | case 'ADD_NEW_ITEM':
168 | ListStore.addNewItemHandler(action.text);
169 | ListStore.emitChange();
170 | break;
171 | default:
172 | // no op
173 | }
174 | })
175 | ```
176 |
177 | In the above code, when receiving the `ADD_NEW_ITEM` action, the callback will operate the `ListStore`.
178 |
179 | Please keep in mind, Dispatcher has no real intelligence on its own — it is a simple mechanism for distributing the actions to the stores.
180 |
181 | ### Stores
182 |
183 | The Stores contain the application state. Their role is somewhat similar to a model in a traditional MVC.
184 |
185 | In this demo, we have a [`ListStore`](https://github.com/ruanyf/extremely-simple-flux-demo/blob/master/stores/ListStore.js) to store data.
186 |
187 | ```javascript
188 | // stores/ListStore.js
189 | var ListStore = {
190 | items: [],
191 |
192 | getAll: function() {
193 | return this.items;
194 | },
195 |
196 | addNewItemHandler: function (text) {
197 | this.items.push(text);
198 | },
199 |
200 | emitChange: function () {
201 | this.emit('change');
202 | }
203 | };
204 |
205 | module.exports = ListStore;
206 | ```
207 |
208 | In the above code, `ListStore.items` is used for holding items, `ListStore.getAll()` for getting all these items, and `ListStore.emitChange()` for emitting an event to the Views.
209 |
210 | The Stores should implement an event interface as well. Since after receiving an action from the Dispatcher, the Stores should emit a change event to tell the Views that a change to the data layer has occurred.
211 |
212 | ```javascript
213 | // stores/ListStore.js
214 | var EventEmitter = require('events').EventEmitter;
215 | var assign = require('object-assign');
216 |
217 | var ListStore = assign({}, EventEmitter.prototype, {
218 | items: [],
219 |
220 | getAll: function () {
221 | return this.items;
222 | },
223 |
224 | addNewItemHandler: function (text) {
225 | this.items.push(text);
226 | },
227 |
228 | emitChange: function () {
229 | this.emit('change');
230 | },
231 |
232 | addChangeListener: function(callback) {
233 | this.on('change', callback);
234 | },
235 |
236 | removeChangeListener: function(callback) {
237 | this.removeListener('change', callback);
238 | }
239 | });
240 | ```
241 |
242 | In the above code, `ListStore` inheritances `EventEmitter.prototype`, so you can use `ListStore.on()` and `ListStore.emit()`.
243 |
244 | After updated(`this.addNewItemHandler()`), the Stores emit an event(`this.emitChange()`) declaring that their state has changed, so the Views may query the new state and update themselves.
245 |
246 | ### Views, again
247 |
248 | Now, we come back to [the Views](https://github.com/ruanyf/extremely-simple-flux-demo/blob/master/components/MyButtonController.jsx) for implementing an callback for listening the Store's `change` event.
249 |
250 | ```javascript
251 | // components/MyButtonController.jsx
252 | var React = require('react');
253 | var ListStore = require('../stores/ListStore');
254 | var ButtonActions = require('../actions/ButtonActions');
255 | var MyButton = require('./MyButton');
256 |
257 | var MyButtonController = React.createClass({
258 | getInitialState: function () {
259 | return {
260 | items: ListStore.getAll()
261 | };
262 | },
263 |
264 | componentDidMount: function() {
265 | ListStore.addChangeListener(this._onChange);
266 | },
267 |
268 | componentWillUnmount: function() {
269 | ListStore.removeChangeListener(this._onChange);
270 | },
271 |
272 | _onChange: function () {
273 | this.setState({
274 | items: ListStore.getAll()
275 | });
276 | },
277 |
278 | createNewItem: function (event) {
279 | ButtonActions.addNewItem('new item');
280 | },
281 |
282 | render: function() {
283 | return ;
287 | }
288 | });
289 | ```
290 |
291 | In the above code, you could see when `MyButtonController` finds out the Store's `change` event occurred, it calls `this._onChange` to update the component's state, then trigger a re-render.
292 |
293 | ```javascript
294 | // components/MyButton.jsx
295 | var React = require('react');
296 |
297 | var MyButton = function(props) {
298 | var items = props.items;
299 | var itemHtml = items.map(function (listItem, i) {
300 | return