dyo / dyo

Dyo is a JavaScript library for building user interfaces.

Home Page:https://dyo.js.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Replacing Pure Components

thysultan opened this issue · comments

Consider the following two pure components

const First = props => <div><h1>1</h1></div>
const Second = props => <div><h1>2</h1><div>

When we render one, then replace it with the other:

render(<First>, 'body')
render(<Second>, 'body')

The current implementation would first remove The <First> component completely, then mount the <Second> component.

However due to the fact that Dyo has gone all in with hooks and removing introspection artefacts like refs for component instances, we can know ahead of replacing the component whether the component is pure(does not use any hook) and short circuit the complete removal and instead opt to do a re-render and reconcile further down the tree.

This translates to instead of replace the component from the step(<div>) we instead replace only the part of it that has changed 1 -> 2 while transitioning to the new component as the new source of truth.

I have this working on a local branch for the next major version (V2).

Not really sure whether this is a good idea in general.
The "pure" Dyo componenent could contain non-pure custom elements.
And then the results could be different than expected.

Please see the following demo - works exactly as expected .... but will not work a as expected if the above written improvement is added to Dyo.
https://jsbin.com/qejeyuyeli/edit?html,js,output

Since 'simplecounter' is not pure, so it would be replaced like normal, what am i missing?

Edit: Didn't notice that "simplecounter" is not a Dyo component, but the point still stands, what is the issue here?

@mcjazzyfunky This is a DOM mutation optimization, where the pure 'parents' can be diffed as if they were the same component. Normally, a different component (First vs Second) requires document.removeElement, whereas updating a component allows document.setAttribute. This change allows pure components to benefit from setAttribute diffing.

Edit: note this only applies to First and Second and not child components

@thysultan @Zolmeister

Maybe even without custom elements there are cases where such an optimization may cause problems.

Please have a look at the following demo:
After the optimization: If you enter a username in the "Login by username" from and then switch to the "Login by e-mail address" form, the field "E-mail address" then will be filled with the value from the "username" field of the other form, won't it? At least I think so (cannot try it of course)... am I wrong?

https://codesandbox.io/s/dry-pond-cf84l

@mcjazzyfunky You are absolutely correct, great point. I thought #120 had implemented this but now I don't think it does.
It seems solvable (a pure-flag and white-list of pure dom nodes during reconcilation), but certainly a significant issue.

.. and custom elements can easily be detected by the hyphens in their tag name => they should always be replaced in such cases as you never known what happens inside of them.

I agree. Thinking about this, we can roll that back and instead designate this heuristic strictly to inline functions, for example:

render(props => h('h1', {}, 'Hello'), document)
render(props => h('h1', {}, 'World'), document)

or

render(h('div', {}, props => h('h1', {}, 'Hello'), document)
render(h('div', {}, props => h('h1', {}, 'World'), document)

etc...

cross-component dom recycling ended up being the source of a lot of headaches for all vdom impls that tried it - preact being the biggest i know. there were dozens of long-standing footguns fixed when they removed that default behavior, e.g. preactjs/preact#1208. admittedly, many were likely due to stateful components getting their dom recycled.

the problem with it is that the dom can hold state that is not always reflected or controlled in js - scroll position, text cursor/selection, validation status, element focus, css transition state etc, etc, etc.

i dont think there's such a thing as a component that can ever be "pure" enough to safely assume its dom can be recycled into another.

right?

The proposed heuristic do not rely on DOM/Component recycling like preact/inferno – assuming i understood what you meant by "recycling".

However the issues brought up in previous comments re(web-components/inputs) are issues the initial proposal falls short of. The follow up proposal(comment) tries to fit in and solve it by pushing this heuristic to only apply to inline functions that are an implicit/explicit buy into the heuristic.

For example:

h('div', condition ? props => h('h1', 'Hello') : props => h('h1', 'World'))

The inline function will always assume a single placement in the tree as opposed to the nominal:

h('div', {}, condition ? h(A) : h(B))

However even then the first example doesn't buy into this heuristic if the function so much as uses any kind of state(useState, useEffect, etc...)