Blog.andrewray. me/flux-for-st… By Andrew Ray
TL; DR When I was trying to learn Flux, I wanted someone to tell me that it wasn’t easy, there wasn’t good documentation to look up, and there were lots of flexible components.
Do I need to use Flux?
If your application needs to handle dynamic data, the answer is yes, and you may need to use Flux.
If your application simply doesn’t need to share a static view of the state, and you never save or update data, then you don’t need to use Flux, and Flux won’t do you any good.
Why Flux?
Skin off, since Flux is a moderately complex idea, why add complexity?
90% of iOS apps are data in tabular views. The iOS toolkit has well-defined views and data models that make application development easy.
But on the front End (Font End: HTML,JS,CSS), we don’t even have that. Instead, we had a big problem: no one knew how to build a front-end application. I’ve been in this business for years and no one ever taught me “best practices”. Instead, they taught me a lot of “libraries” like jQuery, Angular, Backbone, etc. But the real problem, data flow, eluded us.
What is Flux?
Flux is a term used to describe a “one-way” flow of data with very specific events and listeners. There is no official Flux library, but you will need Flux Dispatcher and any JavaScript Event library.
Official documents are written like someone’s stream of consciousness, and it’s not a good place to start. But once you master Flux, it can help you fill in the gaps.
Don’t try to compare Flux to the MVC architecture, the similarities will just confuse you.
Officially in the hole! I will explain the concepts in order and build them one by one.
1. View’s Dispatch and Actions
The Dispatcher is essentially an event system with extra rules added. It broadcasts the event and registers the callback. There is only one global Dispatcher and you should use the Facebook Dispatcher Library. Instantiation is easy:
var AppDispatcher = new Dispatcher();
Copy the code
Suppose your application has a New button to add items to the list.
<button onClick={ this.createNewItem} >New Item</button>
Copy the code
What happens when you click? Your view schedules a very specific “action” that contains the action name and the new project data:
createNewItem: function( evt ) {
AppDispatcher.dispatch({
actionName: 'new-item'.newItem: { name: 'Marco' } // example data
});
}
Copy the code
“Action” is another word Facebook coined. It’s a JavaScript object that describes what we want to do and the data we need to do it. As you can see, all we need to do is add a new-item, and the data we need is the item name.
2.”Store” responds to scheduled operations
Like Flux, the word “Store” was coined by Facebook. For our application, we need a specific logic and data set for the list. This describes our Store, which we’ll call the ListStore.
Store is a single object, meaning you may not be able to declare it with the “new” keyword, and there is only one instance per Store in the application.
// Single object representing list data and logic
var ListStore = {
// Actual collection of model data
items: []
};
Copy the code
The Store then responds to the assigned operation:
varListStore =...// Tell the dispatcher we want to listen for *any*
// dispatched events
AppDispatcher.register( function( payload ) {
switch( payload.actionName ) {
// Do we know how to handle this action?
case 'new-item':
// We get to mutate data!
ListStore.items.push( payload.newItem );
break; }});Copy the code
This is the traditional way Flux handles scheduling callbacks. Each payload contains an action name (actionName) and data (newItem). The Switch statement determines whether the Store should respond to the action and knows how to handle data changes based on the type of action.
🔑 Key point: Store is not a data model, a store contains a model.
🔑 Key point: Store is the only thing in your application that knows how to update data, it is the most important part of Flux. The action we schedule doesn’t know how to add or remove items.
For example, if different parts of your application need to keep track of certain images and their metadata, then you need to create another store and name it ImageStore. A Store is like a separate “domain” within an application, which may already be obvious to you if the application is very large. If the application is small, you may only need one store. In general, one model type corresponds to only one Store.
Only store allows registering Dispatcher callbacks! A view should never call AppDispatcher.register. The Dispatcher should only be used to send messages from the View to the Store. Views respond to different types of events.
3. Store triggers the Change event
Almost done! Now the numbers have really changed and we need to tell the world! The Store triggers an Event, but the Dispatcher is not used. It’s confusing, but that’s the way Flux works. Let’s add the ability to trigger events to our Store. If you’re using MicroEvent.js, it’s simple:
MicroEvent.mixin( ListStore );
Copy the code
The Changes event is then triggered
case 'new-item':
ListStore.items.push( payload.newItem );
// Tell the world we changed!
ListStore.trigger( 'change' );
break;
Copy the code
🔑 Key point: When we trigger the event, we do not pass the latest item. The View only cares that something has changed. Let’s keep looking at the data to see why.
4. The View responds to the Change event
Now we need to show the list. When the list changes, the view is completely re-rendered.
First, listen for the change event from ListStore when the component is “mounted”, that is, when the component is first created:
componentDidMount: function() {
ListStore.bind( 'change'.this.listChanged );
},
Copy the code
For simplicity, we just call forceUpdate, which triggers re-render.
listChanged: function() {
// Since the list changed, trigger a new render.
this.forceUpdate();
},
Copy the code
When a component is “unmounted”, don’t forget to clear the event listener
componentWillUnmount: function() {
ListStore.unbind( 'change'.this.listChanged );
},
Copy the code
Now what? Let’s take a look at my render function, which I’ve deliberately saved for last.
render: function() {
// Remember, ListStore is global!
// There's no need to pass it around
var items = ListStore.getAll();
// Build list items markup by looping
// over the entire list
var itemHtml = items.map( function( listItem ) {
// "key" is important, should be a unique
// identifier for each list item
return <li key={ listItem.id} >
{ listItem.name }
</li>;
});
return <div>
<ul>
{ itemHtml }
</ul>
<button onClick={ this.createNewItem} >New Item</button>
</div>;
}
Copy the code
Now it’s complete. When you add a new item, the View sends out the user’s Action, the Dispatcher receives the Action, asks the Store to update it, the Store changes the data, the Store fires the change event, and the View responds to the change event by re-rendering the page.
But there’s a problem: we re-render the entire view every time the list changes! Isn’t that incredibly inefficient?
Not at all.
Of course, we will call the render function again and make sure that all the code in the render function will be rerunked. However, React will only update the real DOM if the render output has changed. Your render function actually generates a “virtual DOM” and React compares render with the previous output. If the two virtual DOM’s are different, React will update the real DOM only with the difference.
🔑 Key point: When Store data changes, views should not care if things are added, removed, or changed. Views should only be re-rendered. React’s “virtual DOM” difference algorithm addresses these major issues and identifies DOM nodes that actually change. This will make your life easier and lower your blood pressure.
One more thing: What exactly is Action Creator?
Remember, when we click a button, we assign a specific action:
AppDispatcher.dispatch({
eventName: 'new-item'.newItem: { name: 'Samantha'}});Copy the code
Well, if many of your views need to send this action, you can type it again. In addition, all of your views need to know a specific object format. That’s lame. Flux suggests an abstraction, called Action Creator, that simply abstracts the above into a function.
ListActions = {
add: function( item ) {
AppDispatcher.dispatch({
eventName: 'new-item'.newItem: item }); }};Copy the code
Now your view can call listactions.add ({name: ‘… ‘}); Without having to worry about scheduling the object syntax.
PS: Do not use forceUpdate
I’m used to the simplicity of forceUpdate. The correct way for a component to read store data is to copy the data to state and read this.state in the Render function. You can see how it works in the TodoMVC example.
When the component is first loaded, the store data is copied to state, and when the Store updates, the data is copied in its entirety. This is better because internally, forceUpdate is synchronous and setState is very efficient.