Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Keyed children diff optimization#663

Merged
jorgebucaran merged 2 commits into
jorgebucaran:masterfrom
SkaterDad:keyed-child-diff-opts
Apr 6, 2018
Merged

Keyed children diff optimization#663
jorgebucaran merged 2 commits into
jorgebucaran:masterfrom
SkaterDad:keyed-child-diff-opts

Conversation

@SkaterDad

@SkaterDad SkaterDad commented Mar 25, 2018

Copy link
Copy Markdown
Contributor

Summary of the change

  1. Determine if the new keyed child matches the next old child (thanks to preact for the inspiration here).
  2. If the current old child is keyed, leave it alone.
  3. If the current old child is non-keyed, remove it. This keeps the "mixed keyed/nonkeyed" test happy.
  4. Increment the old children index and go back to the start of the while loop.

Benefits

  • Closes Keyed <input> loses focus when earlier sibling is removed #660: Keyed <input> stays focused if siblings before it are removed (Proof on Codepen). I tried writing test cases for this, but the <input> never fired a "blur" event like it did in-browser.

  • Better keyed list modification performance. Results in fewer "insertBefore" calls when a keyed item is removed but other items are in the same order. Open the "Details" below to see a screenshot from JS Framework Benchmark. Row removal is 2x faster. Row swap is 5x faster.

    Details

    image

Bytes

Minified file size changes from 3.45KB to 3.51KB.
Gzipped file size changes from 1.54KB to 1.56KB (using default options in linux gzip command).

Edit: Added benchmark screenshot.

 * Detect when keyed child matches the next old child
 * Results in fewer "insertBefore" calls
 * Keyed <input> stay focuses
@SkaterDad SkaterDad added bug Something isn't working website hyperapp.dev labels Mar 25, 2018
@SkaterDad SkaterDad requested a review from jorgebucaran March 25, 2018 17:07
@codecov

codecov Bot commented Mar 25, 2018

Copy link
Copy Markdown

Codecov Report

Merging #663 into master will not change coverage.
The diff coverage is 100%.

Impacted file tree graph

@@          Coverage Diff          @@
##           master   #663   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files           1      1           
  Lines         161    166    +5     
  Branches       50     52    +2     
=====================================
+ Hits          161    166    +5
Impacted Files Coverage Δ
src/index.js 100% <100%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 808f42d...e2abf67. Read the comment docs.

@jorgebucaran jorgebucaran requested review from andyrj and zaceno March 26, 2018 04:45
@SkaterDad

SkaterDad commented Mar 26, 2018

Copy link
Copy Markdown
Contributor Author

I've done a little more experimenting to see how flexible this change is.

As the code implies, this is an optimization for single item removals. It also handles cases like this, where single items are removed in multiple parts of the list. Here's another demonstration which is automated: https://codepen.io/SkaterDad/pen/WzZPxr

[ 1, 2, 3 ] ==> [ 2, 3 ]
[ 1, 2, 3, 4, 5] ==> [ 2, 4, 5] 

I believe these are common operations in real apps (data-driven tables, dynamic forms, etc..), and are worth optimizing for. The benchmark results are a nice bonus.

This change does not handle multiple consecutive items being removed, or if a non-keyed item is mixed in. The diffing will go back to its current behavior and do a bunch of "insertBefore" DOM operations.

Those cases are where a more complicated diffing algorithm like the one in ivi can make better decisions, but for now I'm happy to write my views in a way that avoids those scenarios when possible.

@jorgebucaran jorgebucaran mentioned this pull request Apr 1, 2018
@SkaterDad

Copy link
Copy Markdown
Contributor Author

@jorgebucaran Do you see this going anywhere before 2.0? I've got code I'm waiting to PR for thunks/lazy VNodes which touches code in the same region. I would imagine 2.0 is touching some of the patch & resolveNode code also?

I'm fine waiting, just curious what your plans are.

@jorgebucaran

jorgebucaran commented Apr 4, 2018

Copy link
Copy Markdown
Owner

@SkaterDad I didn't know this was blocking you, I'll get this merged and published tomorrow.

I would imagine 2.0 is touching some of the patch & resolveNode code also?

It isn't. That algo rewrite is also in the pipeline, but after 2.0. 😅

@SkaterDad

Copy link
Copy Markdown
Contributor Author

No worries! Thanks for getting to it.

Comment thread src/index.js Outdated

while (k < children.length) {
var oldKey = getKey(oldChildren[i])
var nextOldKey = getKey(oldChildren[i + 1])

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SkaterDad Can we inline this call with the if (newKey != null && newKey === nextOldKey) { later below?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You bet. Looks like that shaves ~3 bytes off after gzip.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 👍

@jorgebucaran

Copy link
Copy Markdown
Owner

@SkaterDad If the current old child is non-keyed, remove it. This keeps the "mixed keyed/nonkeyed" test happy.

Do we want to keep those tests happy though? 🤔

@SkaterDad

Copy link
Copy Markdown
Contributor Author

@jorgebucaran Do we want to keep those tests happy though? thinking

If you don't remove the non-keyed child at that point in the code, it stays in the DOM since cleanup doesn't happen until the end and assumes the only children you need to remove are at the end of the oldChildren array.

I think mixed keyed/non-keyed is a nice feature to support. I do find myself only keying specific children sometimes, and it behaves intuitively.

@jorgebucaran jorgebucaran merged commit f16f7fc into jorgebucaran:master Apr 6, 2018
@jorgebucaran

Copy link
Copy Markdown
Owner

Thank you, @SkaterDad! 🎉🎉🎉

@cjh9

cjh9 commented Apr 17, 2018

Copy link
Copy Markdown

@SkaterDad @jorgebucaran The thing I'm building is highly dependent on having non-keyed elements together with keyed elements in the same parent. If it is keyed, I don't want the algorithm to touch it, contrary if it's not the algo can figure out the most efficient way to modify the dom in order to map to the vdom.

@jorgebucaran

Copy link
Copy Markdown
Owner

@cjh9 ...having non-keyed elements together with keyed elements in the same parent...

This hasn't changed.

@infinnie infinnie mentioned this pull request Apr 26, 2018
@jorgebucaran

Copy link
Copy Markdown
Owner

@SkaterDad I published 1.2.6 by the way. Sorry for the incredible delay. 🙇

@SkaterDad

SkaterDad commented May 31, 2018 via email

Copy link
Copy Markdown
Contributor Author

@jorgebucaran jorgebucaran removed the bug Something isn't working label Sep 1, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

website hyperapp.dev

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants