liveBook · Manning

defineComponent

As explained in Build a Frontend Web Framework (From Scratch)

A book-specific JavaScript function that creates component definitions with configurable initial-state initialization and rendering behavior.

defineComponent is a factory function for creating reusable component classes. Give it a render function, and it returns a class whose instances produce that component’s virtual DOM. The same class can be instantiated repeatedly, while each instance can maintain its own state. section 9.2

Build the mechanism around these responsibilities:

  • Accept render and optional state functions as configuration. section 9.3
  • Create a Component class whose render method calls the supplied render function with the component as its context. section 9.3.2
  • Initialize props and state in the constructor; let state(props) customize the initial state for each instance. section 9.3
  • Provide mount, unmount, and updateState methods so instances can enter the DOM, leave it, and patch their rendered view. section 9.3.2
  • Accept additional component methods for event handlers, binding them to the component context when needed. section 10.0

A counter with configurable initial state

const Counter = defineComponent({ state(props) { return { count: props.initialCount ?? 0 } }, render() { return /* virtual DOM based on this.state.count */ } }) const counter = new Counter({ initialCount: 10 }) counter.mount(document.body) The state function receives the instance’s props, so this counter starts at 10 instead of the default 0. Calling updateState({ count: 11 }) merges the new value and patches the view. section 9.3 section 9.3.2

Listing 9.6 The defineComponent() function so far (component.js)
import { destroyDOM } from './destroy-dom'
import { mountDOM } from './mount-dom'
import { patchDOM } from './patch-dom'

export function defineComponent({ render, state }) {
  class Component {
    #isMounted = false
    #vdom = null
    #hostEl = null

    constructor(props = {}) {
      this.props = props
      this.state = state ? state(props) : {}
    }

    updateState(state) {
      this.state = { ...this.state, ...state }
      this.#patch()
    }

    render() {
      return render.call(this)
    }

    mount(hostEl, index = null) {
      if (this.#isMounted) {
        throw new Error('Component is already mounted')
      }

      this.#vdom = this.render()
      mountDOM(this.#vdom, hostEl, index)

      this.#hostEl = hostEl
      this.#isMounted = true
    }

    unmount() {
      if (!this.#isMounted) {
        throw new Error('Component is not mounted')
      }

      destroyDOM(this.#vdom)

      this.#vdom = null
      this.#hostEl = null
      this.#isMounted = false
    }

    #patch() {
      if (!this.#isMounted) {
        throw new Error('Component is not mounted')
      }

      const vdom = this.render()
      this.#vdom = patchDOM(this.#vdom, vdom, this.#hostEl)
    }
  }

  return Component
}

Inspect the accumulated implementation, including props, optional initial state, rendering, mounting, unmounting, and state-driven DOM patching. section 9.3.2

How Build a Frontend Web Framework (From Scratch) covers it

How deep it goes
Builds the factory and lifecycle incrementally, then extends it with props, state, DOM patching, and custom methods.
Examples
Uses CreateTodo, TodoList, Counter, and FlyingButton examples to demonstrate rendering, initialization, state, and interaction.
What it assumes
Assumes JavaScript classes and virtual DOM helpers while developing lifecycle and state behavior incrementally.
What you can do after
Build reusable stateful components from scratch using a configurable JavaScript factory and virtual DOM lifecycle.
How it connects
Connects component classes with virtual DOM, component state, props, DOM mounting, DOM unmounting, and DOM patching.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Build a Frontend Web Framework (From Scratch). The section links point to the parts of the book it draws on.