Core concepts
Ambient accessors
As of 1.14.0, a component's view takes only the model. The context and props are read ambiently instead — at the point in the tree that actually needs them — with vcontext and vprops. vmodel completes the trio.
These three are accessors, not nodes. Each wraps a function from the value to a View. The function is applied and the wrapper discarded whenever the enclosing View is built or rendered, so VContext, VProps and VModel never appear in the virtual DOM the runtime diffs.
The trio
vcontext- read the app-global
context. Synonym:withContext. vprops- read the enclosing component's
props. Synonym:withProps. vmodel- read the enclosing component's
model. Synonym:withModel.
All three share one shape — a function in, a View out:
vcontext :: (context -> View context props model action)
-> View context props model action
vprops :: (props -> View context props model action)
-> View context props model action
vmodel :: (model -> View context props model action)
-> View context props model actionWhy they exist
Before 1.14.0, a view was handed all three values as arguments:
-- before 1.14.0
view :: context -> props -> model -> View context model action
view ctx props model = ...That meant every helper needing the context had to take it as an extra parameter, threaded down through the whole tree — even helpers that used nothing else. Now the view takes only the model, and anything deeper reaches for what it needs, where it needs it:
-- 1.14.0
view :: model -> View Ctx props model action
view model =
H.div_ []
[ H.h1_ [] [ text (title model) ]
, themeBadge -- nothing has to pass it the context
]
themeBadge :: View Ctx props model action
themeBadge =
vcontext $ \ctx ->
H.span_ [] [ text (themeLabel (ctxTheme ctx)) ]Scope: what each one sees
Unlike context, which is one global value, props and model are per-component. That is why both are type parameters of View: the props a VProps sees is statically the props of the Component whose view contains it, and a mismatch is a compile-time error.
- context — the one live value for the whole tree; every nested component agrees on its type.
- props — the enclosing component's own. A child mounted with
mountWithProps/vcompsees its props, never its parent's. - model — likewise the enclosing component's own; a mount boundary forgets the child's model type just as it forgets its props and action.
vmodel and view
The model is the one value the view already receives, so vmodel is a convenience rather than a necessity — these two are equivalent:
view model = H.p_ [] [ text (ms (count model)) ]
view _ = vmodel $ \model ->
H.p_ [] [ text (ms (count model)) ]It earns its keep deeper in a tree, where a helper wants the model but sits several combinators away from the view that was handed it.
Why not ImplicitParams or a Reader?
Both are alternatives for ambient values. ImplicitParams gives the same "read it where you need it" ergonomics, but is a GHC-specific extension whose constraints leak into every helper's signature. A Reader would work on any compiler, but forces a monadic style onto view code that is otherwise plain applicative expressions and lists. The accessors keep the View DSL as ordinary Haskell values: just another constructor, resolved by the runtime, with nothing to lift or thread.
Rendering to HTML
When serialising, a bare View under toHtml is static markup with context ~ (), props ~ () and model ~ (). A view that reads real values is rendered with toHtmlWith, which takes them:
toHtmlWith
:: context
-> props
-> model
-> View context props model action
-> ByteString
toHtmlWith ctx props model (view comp model)This site's prerenderer does exactly that — there is no global context cell to seed, the value is handed to the renderer. See HTML & prerendering.
Try it
The Context and Props pages both have live demos whose views read their values through these accessors.