React &
Flux
Caveat
These notes are based on a spike!
They are not canon.
Spike is not complete.
Links to resources at end.
What's our problem?
Using MVVM scaling complex components becomes hard.
React
The 'V' in MVC
React
- Makes no assumptions about your stack. Want to use Backbone/Angular, go ahead.
- Component focused. "The only thing you do is build components."
- You can easily compose React components.
- Uses a Virtual DOM to control all DOM access.
- One way data flow, no observables, no two-way data binding.
Users
- Atom editor
- Brackets editor
- Firefox devtools
- Facebook/Instagram/NYT
Open sourced for 1 year.
Annoyed some Ember/Backbone core devs.
Angular/Ember looking to copy ideas.
Easy to integrate into existing projects.
Why should we care?
React Properties
- You re-render the entire component on every change
- Minimizes state management within your component.
- You are passed in immutable state (this.props).
- Only one method declares what the rendered UI should look like (render)
An extension to JS exists whichs allows declarative code within a React component.
/** @jsx React.DOM */
var HelloMessage = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
React.renderComponent(<HelloMessage name="John" />, mountNode);
JSX: JS + XML
Don't be fooled this is still JS.
Optional.
No more templates.
"Separation of technology not concerns."
render: function() {
var commentNodes = this.props.data.map(function (comment) {
return (
<Comment author={comment.author}>
{comment.text}
</Comment>
);
});
return (
<div className="commentList">
{commentNodes}
</div>
);
}
Any JS code will work.
So it's a lot more powerful then just templates.
Only one element can be returned from render.
React Properties
- What happens when you change state is local to the component.
- Observables aren't listening to properties.
- You can type check your component inputs (reusable components).
propTypes: {
initialAmount: React.PropTypes.any.isRequired,
dealtAsset: React.PropTypes.string.isRequired,
amountIsValid: React.PropTypes.bool.isRequired,
amountErrorMessage: React.PropTypes.any
},
There must be state
Sometimes components need internal state
Internal state is accessed via this.state
Internal state is modified via this.setState
/** @jsx React.DOM */
var Timer = React.createClass({
getInitialState: function() {
return {secondsElapsed: 0};
},
tick: function() {
this.setState({secondsElapsed: this.state.secondsElapsed + 1});
},
componentDidMount: function() {
this.interval = setInterval(this.tick, 1000);
},
render: function() {
return (
<div>Seconds Elapsed: {this.state.secondsElapsed}</div>
);
}
});
React.renderComponent(<Timer />, mountNode);
After setState
A component and all it's children will be re-rendered
When the data changes, React conceptually hits the "refresh" button.
How can this be done with complex components and not significantly impact performance?
Current user state such as focus or scrolling?
Virtual DOM
- A JS version of the DOM
- Nodes within JSX aren't real DOM nodes so the DOM isn't affect by 'render'
- Allows React to perform virtual DOM diffing
- Server side rendering
Tree Reconciliation
React compares two virtual DOM trees to find the minimum number of operations to move between the two.
- React tries to save CPU time by not diffing completely different nodes. So changing the virtual node type will skip the diff.
- You can provide unique keys that remain stable between renders. React will prompt you! Makes list rendering more efficient.
Providing keys helps React understand when it's dealing with a different node...
Compose Components
render: function() {
var TenorLadderRowDate = caplinx.fxexecution.reacttile.components.tenorladder.TenorLadderRowDate;
var TenorLadderRowButton = caplinx.fxexecution.reacttile.components.tenorladder.TenorLadderRowButton;
return (
<div className="tenor-ladder-row">
<TenorLadderRowButton
ladderRowIndex={this.props.ladderRowIndex} side={'Bid'} />
<TenorLadderRowDate
ladderRowIndex={this.props.ladderRowIndex} />
<TenorLadderRowButton
ladderRowIndex={this.props.ladderRowIndex} side={'Ask'} />
</div>
);
}
Inside the TenorLadderRow React component.
You can return a tree of components. This is what makes React composable: a key tenet of maintainable frontends.
Component Communications
Data can't just flow downwards.
Users act upon child components.
Sometimes parent components (View Controllers) need to be notified.
ExecuteButton component needs to notify its parent that it's been clicked.
Event handlers
- Event handlers can call passed in callbacks.
- Events are standard DOM ones even in IE8.
- Events do not bubble up to root component.
render: function() {
return (
<ExecuteButton
onExecute={this._executeTrade}
/>
);
},
_executeTrade: function() {
console.log('Executed');
}
render: function() {
return (
<button onClick={this._onClick}>
{buttonValue}
</button>
);
},
_onClick: function() {
var rateData = {
...
};
this.props.onExecute(rateData);
}
ExecuteButton
TenorLadderRowButton
Is React enough?
If you are building small to medium applications React on it's own might be enough.
In larger applications where you need to share data between many React components though...
Data has to be migrated upwards until a shared ancestor is found that can pass data back down.
Yuck.
I want those minutes of my life back.
Flux
Application Architecture for Building User Interfaces
...is more of a pattern than a framework
Flux...
Flux applications have three major parts: the dispatcher, the stores, and the views
Flux only allows unidirectional data flow.
It's a half-duplex communication system.
Data flow cannot loop back around.
Utilities
They are the data model layer.
They create, maintain and dispose of subscriptions to server side data.
TenorLadderRowUtility.prototype._onSettlementDateUpdate = function(assetPair, settlementDate) {
this._tenorLadderRowActionCreator.settlementDateReceived(settlementDate);
};
To know how they should control subscriptions they need to listen to the dispatcher.
ActionCreators
ActionCreators provide a semantic dispatcher API.
Facilitate passing data to the dispatcher.
They pass data to the dispatcher in the form of an action.
Actions are the only concept lacking their own class/module. They are simple object literals containing the action data and type.
TenorLadderRowActionCreator.prototype.settlementDateReceived = function(settlementDate) {
this._dispatcher.dispatch({
type: ActionTypes.TENOR_ROW_SETTLEMENT_DATE_RECEIVED,
settlementDate: settlementDate
});
};
TenorLadderRowActionCreator
Dispatcher
Is essentially a global event bus that
- You cannot specify what to listen to.
- You can control the order of execution of event handlers.
You dispatch data via action creators and all listeners registered with the dispatcher are notified.
Stores
The domain model of the application. They contain the application's data and business logic.
They register with the Dispatcher.
Accept updates and reconcile them as appropriate.
They have no mutator methods.
You can only get state from them.
Stores
They are Event Emitters.
When their state changes they emit an event.
Listeners who are interested in the Store state can then retrieve it.
Views
React components.
Expect with less downward flowing data.
The Stores help us out of our scaling problem!
React components can now register with the Stores to be notified of domain model changes.
From the top
- A settlement date update is received by the utility.
- The utility calls the action creator to notify it of the update.
- The action creator creates an action and passes it into the dispatcher.
- The dispatcher notifies all stores with the action.
- The stores handle the update if the action type is of interest.
- Any store that changes its state raises an event.
- Views registered with the store update themselves.
The other direction
Well it's not too different.
The only difference is it's not a utility calling the action creator.
The action creator is called by the view.
From the user
- The user interacts with the view.
- The view calls the action creator to notify it of the user interaction.
- The action creator creates an action and passes it into the dispatcher.
- The dispatcher notifies all stores with the action.
- The stores handle the update if the action type is of interest.
- Any store that changes its state raises an event.
- Views registered with the store update themselves.
There is one issue with "classical" flux.
Once all the flux actors in an application are wired up data flow is simple.
But how do you wire them all up?
MAKE THEM ALL SINGLETONS.
var ChatAppDispatcher = require('../dispatcher/ChatAppDispatcher');
var ChatWebAPIUtils = require('../utils/ChatWebAPIUtils');
var MessageStore = require('../stores/MessageStore');
module.exports = {
createMessage: function(text) {
ChatAppDispatcher.handleViewAction({
type: ActionTypes.CREATE_MESSAGE,
text: text
});
var message = MessageStore.getCreatedMessageData(text);
ChatWebAPIUtils.createMessage(message);
}
};
ChatMessageActionCreators
Flux example
So we need some pattern for dealing with creating objects.
Instances per Component instance
Factories
class TileDependenciesFactory {
constructor(dispatcher) {}
registerActionCreator(actionCreatorName, actionCreatorClass) {}
registerUtility(utilityName, utilityClass) {}
registerStore(storeName, storeClass) {}
getActionCreator(actionCreatorName) {}
getUtility(utilityName) {}
getStore(storeName) {}
getDispatcher {}
}
The react components are passed this in as a prop.
Factory passed into react components.
TileComponent.prototype.onOpen = function() {
var tileDispatcher = new Flux.Dispatcher();
this._tileDependenciesFactory = new TileDependenciesFactory(tileDispatcher);
this._populateFactory();
React.renderComponent(
<Tile factory={this._tileDependenciesFactory} />,
this._mountNode
);
};
TileComponent.prototype._populateFactory = function() {
this._tileDependenciesFactory.registerActionCreator('TenorLadderRow', TenorLadderRowActionCreator);
this._tileDependenciesFactory.registerUtility('TenorLadderRow', TenorLadderRowUtility);
this._tileDependenciesFactory.registerStore('TenorLadderRowDate', TenorLadderRowDateStore);
this._tileDependenciesFactory.registerStore('TenorLadderRowButton', TenorLadderRowButtonStore);
};
componentWillMount: function() {
this.props.factory.getStore('TenorLadderRowButton').addChangeListener(this._onChange, this);
},
_onChange: function() {
this.setState(this._getStore().getState());
}
Factory allows wiring up without static references to classes.
Could allow clients to customise components easily.
Deserializing
Our components can be persisted.
This means our stores will have to return serialized data on initial construction.
How do we get that data into every store being used in a component?
So when a view is first rendered it gets the serialized state from the store and not some default initial state.
We represented component state as an object literal
tile = {
amount: 500,
dealtAsset: 'GBP',
assetPair: 'GBPUSD',
instrument: '/FX/GBPUSD',
tenorladder: [
{
date: {
tenor: "SPOT"
}
}
]
}
Cursors
"way to manage a component's focus on just the state data that it needs to operate."
So when a store is created it's passed in a cursor that has a reference to the state that the store should be able to mutate.
Our additions
- Factory. We no longer depend on singletons and it could allow configuration of components
- Cursors. Serialize/deserialize support also allows to experiment with undo/redo.
- Scoped dispatchers and actions. Reduces the amount of traffic through the components dispatcher, debatable if needed.
Purpose
"Purpose of this spike is the find out whether React provides sufficient productivity and performance gains to replace the existing Presenter/Knockout MVVM solution."
- Understandable ?
- Debuggable ✔
- Integrates with data model (trade, permissions, prices) ✔
- Modular ✔
- Swappable ✔
- Extensible for customers ✔
- All tests fast and reliable (not via DOM)
- Unit testable ✔
- AT's suitable for business owners
- Price update latency
- Render time (e.g. creating 10 new tiles)
- CPU on high update rate
- Trade on right price under high load
- Community support ✔
- Expected lifespan
- Ease of upgrade
- Core maintainers likelihood to stick around
- Our customers will understand it
Subjective
I would use flux/react over current competitors*
No real Ember or Angular experience.
References
http://www.code-experience.com/avoiding-event-chains-in-single-page-applications/
https://docs.google.com/presentation/d/1afMLTCpRxhJpurQ97VBHCZkLbR1TEsRnd3yyxuSQ5YY/edit#slide=id.p
https://github.com/facebook/flux
https://github.com/swannodette/om/wiki/Cursors
https://stash.caplin.com/projects/MOTIF/repos/fxtrader/branches
react-fxtile branch
React & Flux
By briandipalma
React & Flux
Presentation on React & Flux & an FxTile spike using them.
- 2,684