-
-
Notifications
You must be signed in to change notification settings - Fork 15.2k
Ideas from SunshineJS #780
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
How would you implement recording all actions into a log and later replaying them from a serialized log? |
Ah, I had not thought of that. Module-scoped values do make deserialization tricky. I have some ideas with different tradeoffs. I will have to get back to you when I have time to write them up. |
This is the reason I think using discriminated unions for action types is better than making them classes. |
That seems like a good point. If I come up with any relevant ideas around typechecking discriminated unions I will add to the discussion in #290. And I think I will continue to work on Sunshine with the understanding that it has a different set of goals. Thank you for taking the time to read my comments and for responding! |
This is not a bug report or a specific feature suggestion. I have been working on my own framework, SunshineJS, which is based on principles very similar to those in Redux (because those principles are awesome - I love the attention that Redux is getting). I have come up with ideas in Sunshine that seem to me to work well, and I wanted to start a discussion upstream in case any of those ideas are interesting to people here.
In my view the biggest difference between Sunshine and Redux is that I wrote Sunshine specifically to take advantage of typechecking with Flow. In any case where there was a design decision to be made, I went with the implementation that tended to result in informative type errors when things go wrong. I saw that there is some discussion of using Flow in #290. I'm hoping that my experimentation is helpful in that discussion.
Events as classes
Instead of using string constants and action-creator functions, Sunshine uses classes to define action types and parameters associated with actions. I think that this has a few advantanges:
components happen to use the same name for an action type
Classes allow a nice pattern for implementing reducers. Using the TodoMVC example as a reference, if the
addTodo
action is implemented like this (with Flow type annotations):then the idiomatic Sunshine reducer looks like this:
The
on
method takes an action type and a reducer function. (Note that the second argument to the reducer uses destructuring to get thetext
field out of anAddTodo
instance.)on
serves the dual-functions of invoking a reducer only if the incoming action matches the given type, and of allowing the type checker to verify that the reducer action argument matches the structure of that action type. The implementation ofon
looks like this:Because the type of the handler argument references the
AppState
andEvent
types, the type checker is able to ensure that the reducer is compatible with those types. Because theEvent
variable appears in both handler type and in the action-type type, the type checker can verify that that the reducer matches up with the specific action type that it is registered for.This means that if someone removes a parameter from an action, or changes the type of a parameter, the type checker will point to any reducer implementations that have to be updated. The same goes for changes to the shape/type of the app state.
Sunshine uses an
App
instance and routes everything through it. I imagine if the idea of actions-as-classes were adapted for Redux a reducer might look more like this:React.Component subclass
Sunshine implements a subclass of
React.Component
to wire things in a React-ish way. TheSunshine.Component
subclass expects aSunshine.App
instance to be given either as a prop (in the case of a top-level component) or via context (for child components). (A Sunshine component that gets access to an app instance will automatically relay that instance to child components via context.) In my opinion this allows low-boilerplate patterns for connecting state to components, and for dispatching actions. In particular, state is hooked up by implementing a method, and there is no need to explicitly pass an actions map to child components.Setting component state
Adapting the TodoMVC example again, state is relayed to a component via a
getState
method:Sunshine.Component
looks up the current state in the givenSunshine.App
instance to get initial application state, and runs that state throughgetState
to get the initial component state when a component is mounted.Sunshine.Component
also sets a listener so that whenever application state changes, the component'ssetState
method is automatically called with the updated result of running application state throughgetState
. This means that Sunshine pushes state changes into component state, instead of into component props.An upside is that there is no special code needed to re-render components: Sunshine delegates to React's own state handling. But this does mean that a component that implements
getState
cannot have its internal state that is decoupled from application state. However, aSunshine.Component
subclass that does not implementgetState
can have internal state, and will still pass application context to child components. An application can get plenty of flexibility by mixing components that get state from application state, components that manage their own state, and components that are entirely stateless. Incorporating third-party components that use internal state also works fine.Dispatching actions
Actions are emitted using a component method called
emit
:Once again,
emit
delegates to anapp
object that is passed to the root component as a prop, and is passed to child components via context. So this implementation is universal/isomorphic.I mentioned above that using a class to represent an action type allows the type checker to identify reducers that need to be updated after a change is made to the parameters of an action. The same goes for dispatching actions: if someone added required parameters to
AddTodo
, or changed existing parameters, the type checker would inform the programmer thathandleSave
needs to be updated.Assuming that the
App
component is also a subclass ofSunshine.Component
, the whole thing would be kicked off like this:I did not want to irrevocably tie Sunshine to React; so I split Sunshine into a general-purpose module that can be used with any view layer, and another module containing the React-specific
Sunshine.Component
implementation.Where Sunshine falls short
I love the
combineReducers
function in Redux. Redux has nice options for implementing reducers and stores as small components that are composed to form a larger app. In its current form Sunshine is pretty monolithic. I would like to figure out how to implement similar componentization capability in Sunshine.The text was updated successfully, but these errors were encountered: