Mithril.js
Table of Contents
1. "Design Tools"
I know React uses a MVVM architecture for its components. Mithril.js uses MVC explicitly.
1.1. SAM (State-Action-Model)
There is also the State-Action-Model architecture, which is the hot new thing replacing MVC (and MVVM). Basically, as I understand it,
- Actions compute proposals. Actions cannot mutate the properties of the model.
- The models accepts, partially accepts, or rejects the proposal. A
model should be a series of "acceptors" and "reactors".
- Acceptors validate proposals and mutate the state accordingly
- Reactors react to state mutations and are not involved in the processing proposals
- Evaluate whether a next-action is needed
- Compute the new state representation.
Assignments are not mutations, and mutations must be explicit.
The "programming step" is specified by Action -> Model -> State
.
- Actions are functions, with type signature
Action : Event -> Proposal
- Proposals are "proposals to mutate the property values of the Model"
- The Model is an instance of a class (or closure) generally with a single method that is used to receive proposals to mutate the property values; when the mutation is complete, the model invokes the "State" function, whose role is to compute the (control) state, which can also be viewed as the state representation of the model.
- The State function invokes the next-action predicate which computes whether an action needs to be triggered in the current (new) occurrence of a control state.
The idea is that <View params={Model} />
is no different than View = f(Model)
.
Then the purpose of React (and friends) is to decompose the view into a
series of pure functions.
Now, in "traditional/orthodox MVC", it's entirely interactive (as opposed to reactive). The action (nee Controller) would call an update method on the model, then depending on success or failure would determine how to update the view accordingly.
Reactive coding would have actions (controllers) pass values to the model, regardless of the outcome. There is no "decide how to update the model". A reactive MVC would look something like
View = f(Model.present(Action(data)))
- When an action is triggered, it computes a data set from a set of inputs (e.g., user inputs) that is presented to the model, which then decides if and how to update itself.
- Once the update is complete, the view is rendered from the new model state.
- The reactive loop is then closed.
The SAM pattern boils down to the expresion:
View = State(view_model(Model.present(Action(data))), next_action_predicate(Model))
Action
,view_model
, andnext_action_predicate
must be pure functionsnext_action_predicate
is a callback invoked once the state representation has been created and on its way to be rendered to the user.
References:
1.1.1. Example in Mithril.js
Let's examine the following "counter" example from foucist:
const State = () => ({ count: 0 }); const Actions = state => ({ increment: () => state.count += 1, decrement: () => state.count -= 1 }); const state = State(); const actions = Actions(state); const Counter = { view: ({ attrs: { state, actions } }) => m('div', m('h1', 'Counter'), m('p', state.count), m('button', { onclick: actions.increment }, '+'), m('button', { onclick: actions.decrement }, '-') ) }; m.mount(document.body, { view: () => m(Counter, { state, actions }) });
- Streams play a key role here
- An
update
is a stream of "patches" - A
states
stream of states, obtained withscan
on theupdate
stream and applying an "accumulator" - An
actions
object containing functions which we passupdate
, so that those actions trigger state changes. To be explicit, we would haveincrement: (update, amount) => { update((state) => ({ count: state.count + amount})}
(but this is clearly overkill with superfluous boilerplate)
For more on SAM in Mithril, see:
2. References
- James Forbes, Hooks and Streams
- James Adam, Implementing Search-as-you-type with Mithril.js
- https://codingconnects.com/sam-pattern
- https://www.infoq.com/articles/sam-lessons-learned-front-end-reactive-architectures/
- Lisp and Javascript