composor/composi

Version 3.0.0 is coming.

rbiggs opened this issue · 1 comments

We are working on version 3.0.0. This has some breaking changes. This is a complete rewrite of the diff algorithm affecting every other part of Composi. 3.0.0 introduces significantly faster render times.

  1. Adds new virtual node type: Text
  2. Adds two new functions: vnodeFromElement and vnodeFromChild. These convert a server-rendered DOM tree into a virtual node. This allows Composi to reuse server content, adding events and dynamic behaviors.
  3. Due to changes in the diff algorithm, the render function has a new call signature:
import {h, mount, render) from 'composi'
let title = mount(<Title message='New Message'/>, 'header')
/**
 * Notice how we re-capture the component in the `title` variable and we also pass in the reference to the component container:
 */
title = render(<Title message='New Message'/>, title, 'header')
  1. Due to changes in the diff algorithm, we can no longer short circuit the patch process when calling render. This means that the onupdate lifecycle hook will always fire, event if the component did not update. This is possible where the same data is used when re-rendering a functional component. You can use the onupdate callback to check whatever properties you have to determine if the component actually changed.
  2. Class components require the use of the hydrate property to indicate what element in the DOM to hydrate. This tells Composi to create a virtual node from that element and pass it to the patch function.
  3. Unlike functional components, when you use componentWillUpdate or componentDidUpdate in a class component, if the data is the same, meaning the component will not actually change, these hooks will not execute.
  4. The componentWillUnmount now gets passed a done function. This handles the actual unmounting of the class component. That means if you use the componentWilUnmount lifecycle hook in a class component, you have to also pass in the done callback and invoke it. Otherwise, the component will never unmount.

Examples of new behaviors

The new render behavior:

import { h, mount, render } from 'composi'

let input = null
let key = 104
const fruits = [
  { key: 101, value: 'Apples' },
  { key: 102, value: 'Oranges' },
  { key: 103, value: 'Bananas' }
]
function List(props) {
  function init(el) {
    input = el.querySelector('input')
    input.focus()
  }
  function addItem() {
    const value = input.value
    if (value) {
      fruits.push({
        key: key++,
        value
      })
      // Update the component.
      // Pass list from mount and re-capture it.
      // Don't forget to pass in container as last argument.
      list = render(<List fruits={fruits} />, list, 'section')
      input.value = ''
      input.focus()
    } else {
      alert('Please add a value before submitting.')
    }
  }
  return (
    <div onmount={el => init(el)}>
      <p>
        <input type="text"/>
        <button onclick={() => addItem()}>Add</button>
      </p>
      <ul>
        {
          props.fruits.map(item => <li key={item.key}>{item.value}</li>)
        }
      </ul>
    </div>
  )
}

let list = mount(<List fruits={fruits}/>, 'section')

Hydrating a class component:

import { h, Component } 

class List extends Component {
  // Define list component here...
}

// Instantiate list component:
const list = new List({
  state: fruits,
  container: 'section',
  hydrate: '#old-list-from-server'
})

If your class component is a single use one, you can put the hydrate property directly in the constuctor:

import { h, Component } 

class List extends Component {
  constructor(props) {
    super(props) 
    this.state = fruits
    this.container = 'section'
    this.hydrate = '#old-list-from-server'
  }
  // Define rest of list component here...
}

// Instantiate list component:
const list = new List()

How to use componentWilUnmount in a class component:

// This component has a `setInterval` to control clock ticking set up in `componentDidMount`.
// When the component unmounts, we need to clear this in the `componentWillUnmout` hook.
class Clock extends Component {
  // Define component here...

  // Add lifecycle hooks:
  componentDidMount() {
    // Store timer referrence so we can clear it later:
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  // If component is unmounted, end interval loop.
  // Notice the `done` callback.
  // This needs to be invoked last.
  // Otherwise the component will not unmount.
  componentWillUnmount(done) {
    clearInterval(this.timerID)
    done()
  }
}

Launched so closing this.