Skip to content

Instantly share code, notes, and snippets.

@graue
Created December 31, 2014 09:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save graue/299dff653c76a0bd1e5a to your computer and use it in GitHub Desktop.
Save graue/299dff653c76a0bd1e5a to your computer and use it in GitHub Desktop.
Notes on Bill Fisher and Jing Chen's Flux talk
https://speakerdeck.com/fisherwebdev/fluxchat
https://www.youtube.com/watch?v=i__969noyAM
Stores: "fat models", contain all data for a particular domain
Also application logic for that domain
Setup: Register with the dispatcher (a pub/sub system, distributes
actions to all the stores registered to listen to it)
Only input to store is via the callback it hands to the dispatcher
Dispatcher sends action to the store via the store's callback
If anything has changed based on that action, the store emits a change
event for views to come back and check for new data
Public interface: Getters, no setters
Why? Lack of setters makes it easier to reason about possible states
store can be in.
Store gets all actions, may not care about all of them, but has the
option to change its data as a result of them.
Dispatcher doesn't know anything about the store. Only knows it has a
callback it has to call at a specific time.
View will pull changed data from the store into itself.
View doesn't know much about store, only the pieces it's interested
in. Store knows nothing about View.
Store in particular is its own universe.
Dispatcher's register() method has single argument, the callback.
That callback, inside of it, has a large switch statement, so store
can decide what to do based on the action type.
Store updates itself, no one updates it.
Stores are based on node's event emitter to emit the change.
Controller-views are the ones listening to user events on the
application. Like any other React components, but have special job to
listen to change events, and when they hear them, from any store, call
the stores they're interested in, put that data in their state, and
rerender themselves and their children.
eg:
function getStateFromStores() {
return {
messages: MessageStore.getAllForCurrentThread(),
thread: ThreadStore.getCurrent() // Which thread is current?
};
}
var MessageSection = React.createClass({
getInitialState() {
return getStateFromStores();
}
componentDidMount() {
MessageStore.addChangeListener(this._onChange);
ThreadStore.addChangeListener(this._onChange);
}
componentWillUnmount() {
MessageStore.removeChangeListener(this._onChange);
ThreadStore.removeChangeListener(this._onChange);
}
_onChange() {
this.setState(getStateFromStores());
}
...
}
So wait how does MessageStore know the current thread? Does it call
into ThreadStore.getCurrent()? Is that allowed? [answer: yes, see
below about stores depending on others]
When the view responds to a (native) UI event, it'll want to call an
action creator method. Action creators wrap creation of an action and
pass it to the dispatcher.
Just as views can create these actions, a server (API) can create
these actions as well. Actions are the entry point into the system.
Initialization code (when app first launches), call into API, user
interaction, whatevs.
We can also within action creator siphon off data and do a write to
the API, exiting the system as well.
Single store: Actions -> Dispatcher -> Message Store -> Message View
Adding threads?
Create a thread store and thread view.
Actions
|
v
Dispatcher
/ \
v v
Message Store Thread Store
| /--------/ |
v v v
Message View Thread View
Message View listens to both stores, Thread View just the one.
(Except thread view shows most recent message. Isn't that gonna be in
the message store? Do you put it in both stores?)
(Also this seems inefficient at this point. If a view depends on one
tiny piece of a store, it gets called on EVERY change to the store,
and EVERYTHING it needs from the store must be re-copied and the DOM
tree re-diffed.
Example: In Pots, if I have a PlayStatusStore that gets called every
second to update the current time in the track. This will also force
the playlist to diff and consider re-rendering.)
"The thread store is something that the message store needs to know
about", "already a dependency". OK, why wasn't there an arrow in the
diagram then? :) It should be like this:
Actions
|
v
Dispatcher
/ \
v v
Message Store <---- Thread Store
| | |
| /--------/ |
v v v
Message View Thread View
Add another store to track unread threads?
Because we're tracking whether a message has been read in the message
itself, we need to have reference to the messages, and thread store is
already dependency of message store. So to avoid circular dependency,
third store for unread stuff.
All of these stores need to be kept in sync. Dispatcher has a method
called waitFor.
Stores call this inside their callback to say, "I need to wait for
this other store to update first."
So the unread thread store needs to wait for both message and thread
stores to update before it can update itself, as it depends on data in
those stores.
waitFor() sequences updates, allows hierarchy of stores.
start a new conversation (new message, also new thread)
action = {
type: NEW_THREAD,
to: 'Bill',
text: 'Hey Bill',
threadID: '5e93696f', // temporary on client, just so
messageID: '0272fac4', // we have something to refer to it with
}
Thread Store adds this to map of threads:
'5e93696f': {
participants: ['Bill', 'Jing'],
messageList: ['0272fac4'],
}
Message Store adds this to map of messages:
'0272fac4': {
thread: '5e93696f',
author: 'Jing',
text: 'hey Bill',
}
Another new feature: group threads. Add a person to a thread. Action:
type: ADD_TO_THREAD,
threadID: '5e93696f',
newParticipant: 'Chris',
name: 'Christopher Chedeau',
profilePic: 'http://www...'
Thread Store updates map of threads:
'5e93696f': {
participants: ['Bill', 'Jing', 'Chris'],
... rest same as before ...
}
but we don't want to store participant's profile pic, full name etc on
thread store, cuz person can be on multiple threads, and don't want
that getting out of sync. So we add a third, Participant Store,
keeping track of all people we know about on the client. When it sees
add to thread, it adds [or presumably updates?] to its map:
'Chris': {
name: 'Christopher Chedeau',
profilePic: 'http://www...'
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment