http://slides.com/tylergraf/unidirectional-ftw
@tylergraf
github.com/tylergraf
flickr.com/tylergraf
vimeo.com/tylergraf
#wife {
right: 100%;
margin: 0;
}
is awesome.
Action
Controller
Model
View
Action
Controller
Model
View
Model
View
Model
View
Model
View
Model
View
Model
View
isn't so awesome.
is still awesome.
Controller
Model
View
Model
View
Model
View
Model
View
Model
View
Model
View
Controller
Controller
Action
Action
Action
...and poking is still weird.
Dispatcher
Store
View
Action
Other stuff
Action
{
actionType: 'UPDATE_PERSON',
person: {
id: '1',
name: 'Fabio'
}
}
var dispatcher = new Dispatcher();
var action = {
actionType: 'UPDATE_PERSON',
person: {
id: '1',
name: 'Fabio'
}
}
dispatcher.dispatch(action);
Dispatcher
var _people = [];
dispatcher.register(function(payload) {
switch (payload.actionType) {
case 'UPDATE_PERSON':
_people = _people.map(p=>{
if(p.id === payload.person.id){
return payload.person;
}
return p;
});
break;
case 'DO_SOMETHING':
// do something else
break;
}
PersonStore.emitChange();
});
Store
var _people = [];
var PersonStore = Object.assign(EventEmitter.prototype, {
getPersonById: function(id) {
return _people.find(id);
},
emitChange: function() {
this.emit('change');
},
addChangeListener: function(callback) {
this.on('change', callback);
},
removeChangeListener: function(callback) {
this.removeListener('change', callback);
}
});
dispatcher.register(function(payload) {
switch (payload.actionType) {
case 'UPDATE_PERSON':
_people = _people.map(p=>{
if(p.id === payload.person.id){
return payload.person;
}
return p;
});
break;
case 'DO_SOMETHING':
// do something else
break;
}
PersonStore.emitChange();
});
Store
var PersonView = React.createClass({
getInitialState: function() {
return PersonStore.getPersonById(1);
},
componentDidMount: function() {
PersonStore.addChangeListener(this._onChange);
},
componentWillUnmount: function() {
PersonStore.removeChangeListener(this._onChange);
},
render: function() {
return (
<PersonView personObj={this.state.person} />
);
},
_onChange: function() {
this.setState(PersonStore.getPersonById(1));
}
});
View
Dispatcher
Store
View
Action
Other stuff
Dispatcher
Store
View
Action
Store
Store
View
View
View
View
View
Dispatcher
Store
View
Action
Other stuff
Reducer
Action
{
type: 'UPDATE_PERSON',
person: {
id: '1',
name: 'Fabio'
}
}
var PersonStore = Redux.createStore(reducer);
var action = {
type: 'UPDATE_PERSON',
person: {
id: '1',
name: 'Fabio'
}
}
PersonStore.dispatch(action);
Dispatcher
var dispatcher = new Dispatcher();
var action = {
actionType: 'UPDATE_PERSON',
person: {
id: '1',
name: 'Fabio'
}
}
dispatcher.dispatch(action);
Flux
Redux
Reducer
var _people = [];
dispatcher.register(function(payload) {
switch (payload.actionType) {
case 'UPDATE_PERSON':
_people = _people.map(p=>{
if(p.id === payload.person.id){
return payload.person;
}
return p;
});
break;
}
PersonStore.emitChange();
});
function PersonReducer(state, action) {
if(action === undefined) return state;
switch (action.type) {
case 'UPDATE_PERSON':
state._people = state._people.map(p=>{
if(p.id === action.person.id){
return action.person;
}
return p;
});
break;
}
return state;
};
Flux
Redux
PersonStore.addChangeListener(this._onChange);
PersonStore.removeChangeListener(this._onChange);
AppStore.subscribe();
AppStore.unsubscribe();
Flux
Redux
View
dispatcher.register(PreferencesStateHandler);
dispatcher.register(PedigreeStateHandler);
...
PreferencesStore.getPreference('pref');
//{
// pref object
//}
PedigreeStore.getPedigreeById(1);
//{
// pedigree object
//}
var stores = {
pedigree: PedigreeReducer,
prefs: PreferencesReducer
};
var AppStore = Redux.combineReducers(stores);
AppStore = Redux.createStore(AppStore);
...
AppStore.getState();
//{
// prefs: {
//
// },
// pedigree: {
//
// }
//}
Flux
Redux
function getHighestNumber(max, current){
return Math.max(max, current);
}
var numbersArr = [0,1,2,7,3,4,5,1,2,3,4,5,6,6];
numbersArr.reduce(getHighestNumber);
//7
function add(runningTotal, current){
return runningTotal + current;
}
var numbersArr = [0,1,2,7,3,4,5,1,2,3,4,5,6,6];
numbersArr.reduce(add);
//49
function reducer(state, action) {
switch (action.type) {
case 'DO_THINGS':
state.doing = 'things';
break;
case 'DO_STUFF':
state.doing = 'stuff';
break;
}
return state;
};
AppStore.dispatch({type: 'DO_STUFF'});
AppStore.dispatch({type: 'DO_THINGS'});
AppStore.getState();
//{
// doing: 'things'
//}
dispatcher.register(function(payload) {
switch (payload.actionType) {
case 'FETCH_PERSON':
PERSON_API.getPerson(payload.person.id);
break;
}
PersonStore.emitChange();
});
function PersonReducer(state, action) {
if(state === undefined) return state;
switch (action.type) {
case 'FETCH_PERSON':
state.fetchingPerson = true;
break;
}
return state;
};
var PersonStore = Redux.createStore(PersonReducer);
Flux
Redux
<dom-module id="person-element">
<template>
<style>
:host {display: block;}
</style>
<p class="name">[[person.name]]</p>
</template>
<script>
Polymer({
is: 'person-element',
properties: {
personId: String,
person: {
type: Object
}
},
// Element Lifecycle
ready: function() {},
attached: function() {
AppStore.subscribe(this._onChange);
},
detached: function() {
AppStore.unsubscribe(this._onChange);
},
_onChange: function() {
var state = AppStore.getState();
this.set('person', state.person);
}
});
</script>
</dom-module>
<person-element person-id="1"></person-element>
var store = Redux.createStore(function(state, action) {
return state;
});
var ReduxBehavior = PolymerRedux(store);
var MyElement = Polymer({
is: 'my-element',
behaviors: [ ReduxBehavior ],
created: function() {
var state = this.getState();
this.dispatch({type: "DO_STUFF"});
}
});
Polymer({
is: 'person-element',
behaviors: [ ReduxBehavior ],
properties: {
person: {
type: Object,
statePath: 'person'
}
}
});
function PersonReducer(state, action) {
if(state === undefined) return state;
switch (action.type) {
case 'UPDATE_PERSON':
state.person = action.person;
break;
}
return state;
};