Skip to content

Improving the render cycle API #118

@johan-gorter

Description

@johan-gorter

You can write your whole application without doing anything with the render cycle. This is part of our philosophy of keeping things pure and simple. But you do need the render cycle if:

  • You integrate components/widgets that are not written using maquette.
  • Do advanced animations.
  • Interact with the DOM API.
  • Measure nodes.

Every time a new screen is rendered, the following phases take place:

  • Render render() functions are called
  • Diff and patch The real DOM is updated
  • Measure The phase for callbacks that need to take measurement of the layouted DOM (getBoundingClientRect)
  • WrapUp Phase in which the DOM may be modified again

It is important to have a separate measure and wrapUp phase, because if multiple components were to measure and change the DOM in the same phase, unneeded reflows take place which would hurt performance.

Every time a render takes place, a new RenderRun object is created. All callbacks that are called during a render get a reference to the RenderRun object as a parameter. The RenderRun has the following interface:

Interface RenderRun {
  duringMeasure(callback: () => void): void;
  duringWrapUp(callback: () => void): void;
}

The RenderRun can then be used as follows:

h('div', { enterAnimation: growToAutoHeight })

let growToAutoHeight = (element: Element, run: RenderRun) => {
  let autoHeight = 0;
  run.duringMeasure(() => {
    autoHeight = element.getBoundingClientRect().height;
  });
  run.duringWrapUp(() => {
    element.style.height = '0px';
    let grow = element.animate([{height: '0px', height: autoHeight+'px'}]);
    grow.onfinish(() => {element.style.height = ''})
  })
}

The following phases execute when a render takes place:

  1. render() functions are executed.
  2. Diffing and patching of the real DOM. For each DOM node that is processed, the following happens:
    1. afterFirstRender callback is called if the DOM node is new
    2. afterRender callback is called
    3. enterAnimation is called if the DOM node is new and its parent already existed
    4. updateAnimation is called if DOM node is updated
    5. The DOM node is attached to the parent DOM node if DOM node is new
    6. exitAnimation (from previous render) is called if DOM node is removed and its parent remains
  3. Callbacks on RenderRun.duringMeasure are executed
    duringMeasure callbacks may be used to measure the DOM (getBoundingClientRect), but may not change the DOM.
  4. Callbacks on RenderRun.duringWrapUp are executed
    duringWrapUp callbacks may change the DOM, but should not measure the DOM.

When all code uses the RenderRun object appropriately, all updates to the UI should never cause more than 2 reflows.

Migration path from afterCreate and afterUpdate:

AfterCreate can be replaced with afterFirstRender. Note that during afterFirstRender, the DOM Node is not attached to its parent. If the afterCreate code needed this, afterFirstRender can register code to run during measure or wrapUp.

AfterUpdate can be replaced with afterRender. Note that afterRender also runs when the DOM node is first created and at that time it will not have a parent Node.

This is one of our ideas for maquette 3

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions