@playframe/component
v1.1.1
Published
Pure Stateful Styled Components
Downloads
1
Maintainers
Readme
Component
0.3 kB Pure Stateful Styled Components
Installation
npm install --save @playframe/component
Description
Pure Functional Styled Web Components for PlayFrame. Components are rendered independetly from the rest of the app. By using Shadow DOM provided by ShaDOM we achieve scoped styling and simple css selectors. Please consider using CSS Variables for maximum animation performance and flexibility.
You can create instances manually and keep them in parent state or
you can register them with fp.use
or h.use
and create them dynamically.
To be able to identify the same dynamic component we use a unique
object mkey
as a
WeakMap
key. For a UserView
Component actual user
object would be a perfect
WeakMap key
Usage
import h from '@playframe/h'
import oversync from '@playframe/oversync'
import component from '@playframe/component'
const sync = oversync(Date.now, requestAnimationFrame)
const Component = component(sync)
export default myCounter = (Component)=>
Component({
seconds: 0,
_: {
reset: ()=> {seconds: 0}
increment: (x, state)=> {
state.seconds++
setTimeout( state._.increment,
(1000 - Date.now() % 1000) || 1000 )
}
}
})((state)=>
<my-counter style={ {'--border': state.border} }>
<style>{`
:host {
display: block;
border: var(--border, 0);
}
`}</style>
<div>
{props.children}
<br/>
{state.seconds} seconds passed
<br/>
<button onclick={state._.reset}> Reset </button>
</div>
</my-counter>
)
// our Counter instance with initial props
const MyCounter = myCounter(Component)({seconds: 42})
// reset in 10 seconds
setTimeout(MyCounter._.reset, 10000)
const view = ()=>
<MyCounter border="1px solid grey">
<h1>Hello</h1>
</MyCounter>
// or we can register component as custom element
h.use({'my-counter': (props)=>
let mkey = props && props.mkey
makeMyCounter(Component)({mkey})(props)
})
// mkey is used as WeakMap key to cache our statefull component
const mkey = {uniqueObject: true}
const view = ()=>
<my-counter mkey={mkey} border="1px solid grey">Hello</my-counter>
Annotated Source
We are going to use statue for managing state of our Component
evolve = require '@playframe/evolve'
statue = require '@playframe/statue'
How about using a tree of
WeakMap
instances to cache our Component instances by mkey
?
This allows us to cache our components aggresively because our _cache
will be cleaned automatically by Garbage Collector if mkey
gets dereferenced
Let's export a higher order function that takes sync
engine,
state_actions
for statue and a pure view
function.
module.exports = (sync)=>(state_actions)=>(view)=> _cache = new WeakMap; (upgrade)=>
if (mkey = upgrade and upgrade.mkey) and
Component = _cache.get mkey
return Component
_v_dom = null
_props = null
_rendering = false
_state = evolve state_actions, upgrade
Creating a statue that will deliver the latest state on
sync.render
and patch shadow DOM if needed
_state = statue _state, sync.render, (state)=>
_state = state
do patch_shadow unless _rendering
_rendering = false
return
patch_shadow
is responsible for producing new virtual DOM and using
patch
method for shadow DOM mutation provided by
ShaDOM.
patch_shadow = =>
{patch} = _v_dom
_v_dom = view _state
_v_dom.patch = patch
patch _v_dom
return
render = =>
_v_dom = view _state
attr = _v_dom[1] or= {}
attr.attachShadow or= mode: 'open'
return
Here we create our Component
function that mimics your view
function. But first it's checking if props
are meant to update _state
Component = (props)=>
if _v_dom
unless props is _props
# shallow equality check
for k, v of props when v isnt _state[k]
# updating state with props and rendering
_state = _state._ _props = props
_rendering = true
do render
break
else # first run
_state = Object.assign _state._(), _props = props
do render
_v_dom
Assigning high level methods from statue, adding instance to cache and our
fresh Component
is ready!
Component._ = _state._
_cache.set mkey, Component if mkey
Component