Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improving the render cycle API #118

Open
johan-gorter opened this issue Sep 7, 2017 · 4 comments
Open

Improving the render cycle API #118

johan-gorter opened this issue Sep 7, 2017 · 4 comments
Milestone

Comments

@johan-gorter
Copy link
Contributor

johan-gorter commented Sep 7, 2017

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

@jcfranco
Copy link
Contributor

jcfranco commented Oct 4, 2017

@johan-gorter For the WrapUp phase, is the idea to only modify the DOM directly (i.e., no VDOM)? For example, if during Measure I know that I need to add/remove a CSS class, would I do it directly on the element or would VNode#class come into play?

@johan-gorter
Copy link
Contributor Author

johan-gorter commented Oct 4, 2017

@jcfranco The VNode should not be modified, once it is passed to maquette. Maquette keeps a reference to this VNode to do a diff during the next run.

The wrapUp phase should only modify the real DOM. The data that is used to render the VNode-tree may be modified during measure or wrapUp.

For example: if a UI Component renders itself it may leave a duringMeasure callback to measure if it fits on the screen properly. If it does not fit on the screen, it may leave a duringWrapUp callback to add a compact css class to the component.
But if the component needs a totally different UI in compact mode, the duringMeasure may instead change an internal compact variable and trigger a scheduleRender, so the render function is executed again.

@jcfranco
Copy link
Contributor

jcfranco commented Oct 4, 2017

@johan-gorter That makes sense, thanks for the explanation.

@johan-gorter johan-gorter modified the milestones: 3.0, 4.0 Oct 20, 2017
@johan-gorter
Copy link
Contributor Author

We decided it would benefit everyone if we finish the other 3.0 issues first, so everyone can profit from these and to delay this issue bit, because this has more impact on codebases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants