Skip to content

Instantly share code, notes, and snippets.

@virtualandy
Forked from mcmire/react_ecosystem.md
Last active September 2, 2015 04:19
Show Gist options
  • Save virtualandy/52b5b8dda7d5558ed211 to your computer and use it in GitHub Desktop.
Save virtualandy/52b5b8dda7d5558ed211 to your computer and use it in GitHub Desktop.
Notes on the React ecosystem: Flux, Reflux, Nuclear.js, and more

Following are notes I've compiled while researching the React ecosystems and frameworks/tools that people use within it.

Flux

The "Big Idea" behind Flux is that in order for any change to take effect in the system -- in order for something to change on screen as a result of data changing underneath -- that change must be initiated by an "action". There's only one direction to the flow, so the view layer can listen to the model layer (stores) for changes, but not the other way around. Decoupling is a strong design pattern present throughout Flux, where each object in the system acts independently of other objects.

That's really it, although there's one difference from MVC: the dispatcher. There's only one dispatcher, and all stores register themselves with that dispatcher. The goal of the dispatcher is to govern dependencies on multiple stores, restrict the flow so that no two changes can be processed at once, and to prevent circular changes.

The dispatcher only calls callbacks, it doesn't do anything itself. These callbacks are registered in stores, and dispatched in "action creators" (called in views). Furthermore, the callbacks themselves take one action at a time, but they could have logic to handle multiple actions (switching on the action type).

Since stores are the only things that register callbacks, inside of one of these callbacks you can wait for another store to finish running its updates. When the dispatcher sees this it will stop running the current callback, switch to that callback, and then come back. This is what is meant by "no two changes can be processed at once".

Furthermore, if Store A waits for Store B to finish, but Store B waits for Store A to finish then the dispatcher will detect this and throw an error.

More notes

  • Callbacks registered with the dispatcher and callbacks registered on stores run independently of each other, although by convention stores emit their change event within action handlers, so that the flow (action -> dispatcher -> store -> component) is kept intact. But the flow happens in two steps: first an action dispatches an event across stores that are listening to that event; then those stores dispatch events across components that are listening to those stores to change.
  • When a component accesses data from stores, this also happens independently -- stores choose which data they want to expose via methods, and components choose which methods they want to call on the stores.

Read more

Reflux

Reflux is a simpler version of Flux. You still have actions and stores, but there's no dispatcher -- the job the dispatcher did is now inside of the actions. Additionally, whereas using Flux's dispatcher you had the ability to depend on multiple stores and designate the order that those stores were run by using #waitFor, using Reflux you can simply listen for that store to change. (I'm not sure if I quite understand how this solves the problem, but whatever.)

In other words, both actions and stores are listenable, bringing Flux more in line with something like Backbone. The data flow still starts with actions, though.

I'm not sure if there's a circular dependency checker.

Actions

  • An action is a dumb function that represents something that can happen.
  • It has one #listen method which is used to register callbacks from the outside.
  • Calling the action function then runs all of the callbacks.

Stores

  • A store is an object that responds to actions.
  • It can #listenTo another listenable, and other things can #listen to it.
  • In the init method for the store, it will usually registers one of its methods with one or more listenables. (A listenable is either an action or another store altogether.)
  • When a store changes, it must call #trigger, which will run all of its registered callbacks.

Components

  • A component can then listen to one or more stores such that when they change, the component's internal data changes too. This is done using Reflux.connect.

Read more

Nuclear.js

Nuclear introduces some interesting ideas:

  • You still have multiple stores, but those stores are kept all together in an immutable hash.
  • Stores are completely immutable -- they don't hold any state. They simply expose functions that receive the current state, transform it, and return a new representation of that state. Nuclear will then use the new state to update (actually selectively replace) the composite store.
  • If you want to access data across multiple stores, you use getters. Actually, that's the idiomatic way of accessing data anywhere. Because there's another data access layer here, it means that stores don't need to know about other stores in order to be updated, which means that we don't need the #waitFor mechanism that Flux has. Getters can combine values from other getters and dependency-inject those other getters into your getter.
  • Components listen to getters, and when those getters change then the component's #setState method is automatically called (which will then automatically re-render the component).
  • Nuclear does have a dispatcher and this is called the reactor; it is a singleton. Like Flux, it's a giant registry of callbacks.

To recap:

  • Components listen for getters to change, and then automatically re-render themselves.
  • Getters access all stores as a whole.
  • Stores listen for events to take place, and update themselves using pure functions.
  • The reactor consolidates stores and dispatch events to stores that have registered callbacks on those events.
  • Actions tell the reactor to dispatch events out to stores.

Other features:

  • You can ask the reactor for the value of a store or a value within a store. This is done by passing a "key path" to Reactor#evaluate or #evaluateToJs.
  • You can observe a store or value within a store using the reactor.
  • You can read the entirety of the composite store as a JavaScript object, or if you need to load data into stores you can do that too.

See also

Redux

Redux is similar to Nuclear, and at the same time borrows the idea of "updaters" from Elm.

  • In Nuclear you have multiple stores and those stores listen to events emitted by a single reactor.
  • In Redux, you only have one store, and the reactor/dispatcher is integrated into the store.
  • Like Nuclear, that one store holds all of the data for your app.
  • A store is instantiated with a callback called a "reducer", and that reducer can handle multiple actions.
  • The reducer takes an existing state and an action and returns a new state.
  • The existing state of the application is never changed. This means that you can do things like roll back the state very easily.
  • The only way to mutate the internal state is to call #dispatch on that store with an action.
  • As your app grows, instead of adding more stores, you add more, smaller reducers.

Read more

More Things to Read About

  • Immutable.js -- used by Nuclear and Redux to store and manipulate state.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment