Create view components using a virtual DOM
A library for creating UI components using virtual DOM as an alternative to React. Deku has a smaller footprint (~10kb), a functional API, and doesn't support legacy browsers.
npm install deku
You can also use Duo, Bower or download the files manually.
Components are just plain objects that have a render function instead of using classes or constructors:
// button.jslet propTypes =kind:type: 'string'expects: 'submit' 'button'let props state = componentreturn <button class="Button" type=propskind>propschildren</button>let props state = componentif !stateclickedupdateState clicked: trueexport default propTypes render afterUpdate
Components are then rendered by mounting it in a tree:
import Button from './button'import treerenderrenderString from 'deku'let app = tree<Button kind="submit">Hello World!</Button>renderapp documentbody
Trees can be rendered on the server too:
let str = renderStringapp
Each element of your UI can be broken into encapsulated components. These components manage the state for the UI element and tell it how to render. In Deku components are just plain objects:
let props state = componentreturn <button class="Button">propschildren</button>export default render
There is no concept of classes or use of
this. We can import this component using the standard module syntax:
import Button from './button'
To render this to the DOM we need to create a
tree. This is one of the other main differences between React and Deku. The
tree will manage loading data, communicating between components and allows us to use plugins on the entire application.
import elementtree from 'deku'var app = tree<Button>Hello World</Button>
app object has only a couple of methods:
.set(name, value)to set environment data
.option(name, value)to set rendering options
.mount(vnode)to change the virtual element currently mounted
.use(fn)to use a plugin. The function is called with the
You can render this tree anyway you like, you just need a renderer for it. Let's use the DOM renderer for the client:
import Button from './button'import elementtreerender from 'deku'var app = tree<Button>Hello World</Button>renderapp documentbody
And render the same thing to a string on the server:
import koa from 'koa'import elementtreerenderString from 'deku'let app = koaappusethisbody = renderStringtree<Button>Hello World</Button>
And you can isolate functionality by using plugins. These plugins can call
set to add data to the tree that your components can then access through their props:
You can compose components easily by just requiring them and using them in the render function:
import Button from './button'import Sheet from './sheet'return<div class="MyCoolApp"><Sheet><Button style="danger">One</Button><Button style="happy">Two</Button></Sheet></div>
Deku doesn't use any form of synthetic events because we can just capture every event in newer browsers. There are special attributes you can add to virtual elements that act as hooks to add event listeners:
let props state = componentreturn <button onClick=clicked>propschildren</button>alert'You clicked it'
You can view all event handlers in code.
Just like the
render function, component lifecycle hooks are just plain functions:
let props state = componentif !stateclickedupdateState clicked: true
We have hooks for
beforeUnmount and two new hooks -
afterRender that are called on every pass, unlike the update hooks. We've found that these extra hooks have allowed us to write cleaner code and worry less about the state of the component.
You can validate the props sent to your component by defining a
let propTypes =style:type: 'string'expects: 'submit' 'button'danger:type: 'boolean'optional: true
To enable validation you just need to enable it on the tree:
This is off by default and we've made it an option so that you can enable it just during development without needing a separate build.
Props can originate from anywhere in the outside world, it's useful to validate them. When validation is enabled you'll only be able to pass in props that are defined and they must conform to the
It's often useful for components to have access to data from the outside world without needing to pass it down through components. You can set data on your
tree and components can ask for it using
First we set some data on the app:
appset'currentUser'id: 12435username: 'anthonyshort'name: 'Anthony Short'
Then in our components we define the prop using the
let propTypes =user:source: 'currentUser'
Whenever we change that value in our app all components that depend on it will be re-rendered with the latest value. We use this pattern to pass functions down to interact with the API:
Which the component can access using
props.updateProject. Although it may not be as complex or optimized as Relay and GraphQL it's extremely simple and covers most use cases we've run into so far. We even use this pattern to treat the router as a data source:
routeron'/projects/:id'let project = apiprojectsgetparamsidappset'currentRoute'name: 'view project'project: project
This means we don't need to use some complex routing library. We just treat it like all other types of external data and components will render as needed.
Sometimes when you're rendering a list of items you want them to be moved instead of trashed during the diff. Deku supports this using the
key attribute on components:
let items = componentpropslet projects = itemsmapreturn <ProjectItem key=projectid project=project />return <div class="ProjectsList">projects</div>
At the moment we only support the
key attribute on components for simplicity. Things become slightly more hairy when moving elements around within components. So far we haven't ran into a case where this has been a major problem.
The purpose of most lifecycle hooks is usually to update the state, either by inspecting the DOM or fetching some external resources. We can simplify the concept of the lifecycle hooks by making the pure using ES7 async functions.
asyncvar items = await requestpropsurlvar projects = await ProjectsgetAll// Return an object to update statereturnitems: itemsprojects: projectsloaded: true
Instead of using the
updateState function we can just return an object that will be merged in with the current state. We can do this because the lifecycle hooks are able to return a promise that resolves into a state change. All you need to do is return a promise and resolve it with an object.
We could do this with standard promises too:
return requestpropsurlthenProjectsgetAllthenreturnitems: itemsprojects: projectsloaded: true
Deku is built with Browserify. You can run the tests in a browser by running
MIT. See LICENSE.md