Skip to content

Instantly share code, notes, and snippets.

@anthonyshort
Last active February 22, 2023 06:45
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save anthonyshort/dd3b115c2499b53e1e13 to your computer and use it in GitHub Desktop.
Save anthonyshort/dd3b115c2499b53e1e13 to your computer and use it in GitHub Desktop.
Functional API for Deku
  • Prefer composition over mixins
  • Prefer immutablity
  • Avoid using events as they create side-effects
  • Lifecycle methods should always be a single function. e.g. button.beforeMount(cxt)
  • Declarative API with everything declared the same way in the same place
  • Avoid the need for deku-specific plugins, it should make composition easy
  • Avoid magic and complex APIs. Functions should just be functions with no side-effects.
  • How do we handle things that always need to do something at the start and end of the component lifecycle? Intervals and pulling in data.
  • How can we make all the hooks be declared the same way while still allowing composition and having simple functions?
import {component,dom} from 'deku'
let button = component('My Button')
.prop('foo', { type: 'string', optional: true, expects: ['hello', 'goodbye'] })
.prop('bar', { type: 'boolean', default: false })
.observe('projects', store('projects'))
.observe('timer', interval(1000))
.use(channel('projects'))
.use(pure())
// button.use(compose('beforeMount', channel('eventTypes'), interval('foo', 1000), somethingElse))
// button.beforeMount(context)
// Methods
button.render = function(context){
let {el,children,data} = context
let {foo,bar} = context.props
let {open,enabled} = context.state
// context.data.projects
return (
<button onClick={click}>Submit</button>
)
}
button.beforeMount = function(context){
}
button.initialState = function(){
return {
open: true
}
}
button.afterMount = function(context){
context.body.appendChild(context.el)
context.setState({ 'foo': 'bar' })
}
button.beforeUnmount = function(context){
}
// Handlers
function click(context, event){
event.preventDefault()
context.setState({ foo: 'bar' })
}
function beforeMount(context) {
}
export {button}
// Allows composing multiple fns together into a middleware chain
// button.use(compose('beforeMount', store('events'), store('events'), prepare))
function compose(name, ...fns) {
var mw = ware().use(fns)
return function(Component) {
Component[name] = function(context) {
mw.run(context)
}
}
}
export {compose}
import {button} from './button'
import {scene,render} from 'deku'
// returns a scene
var app = scene(button)
.setProps({ text: 'Submit' })
.use(channels())
.debug(true)
// Render the scene to the body using the DOMRenderer
// Returns the renderer object.
render(app, document.body)
// Update the props. The renderer will re-render
// the app on the next frame.
app.setProps({ text: 'foo' })
// Override shouldUpdate to provide a small performance
// boost for components with pure rendering functions
function pure() {
return function(Component) {
Component.shouldUpdate = function(props, state, prevProps, prevState) {
return false;
}
}
}
export {pure}
// Observe external data sources
function store(name) {
return function(context) {
let store = context.stores[name]
return store.subscribe(context) // returns an Observable
}
}
export {store}
import {button} from './button';
import {context} from 'deku';
// We can execute the middleware in tests and assert the
// hooks have updated the context to the correct state
let cxt = context()
button.afterMount(cxt)
assert(cxt.props.foo)
// Check the result of the render method and test it to
// make sure events are hooked up, the structure is correct etc.
let cxt = context()
let result = button.render(cxt)
// Or make sure your shouldUpdate method works correctly
var shouldUpdate = button.shouldUpdate(props, state, prevProps, prevState)
assert(shouldUpdate)
@anthonyshort
Copy link
Author

If the hooks needed to return a context or a promise that would resolve into a context, we could allow the contexts to be immutable and async.

button.beforeMount(context).then(function(newContext){
  if (context !== newContext) {
    emit('change')
  }
})
var newContext = button.beforeMount(context)
var newContext = await button.beforeMount(context)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment