Skip to content

Commit

Permalink
✨ add diff and patch
Browse files Browse the repository at this point in the history
  • Loading branch information
nem035 committed Jan 4, 2019
1 parent 1bfd328 commit b2fa5dc
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 6 deletions.
14 changes: 8 additions & 6 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { createElement, render } from './vdom';
import mount from './vdom/mount';
import { createElement, render, mount, diff } from './vdom';

const createApp = ({ count }) =>
createElement(
Expand All @@ -10,6 +9,7 @@ const createApp = ({ count }) =>
},
[
String(count),
createElement('input', { type: 'text' }),
createElement('img', {
src: 'https://media.giphy.com/media/l1IYk3l6xcEDN5RFS/giphy.gif',
}),
Expand All @@ -20,11 +20,13 @@ const state = {
count: 0,
};

let $root = document.querySelector('#app');
$root = mount(render(createApp(state)), $root);
let vApp = createApp(state);
let $root = mount(render(vApp), document.querySelector('#app'));

setInterval(() => {
console.log(`rendering count ${state.count}`);
$root = mount(render(createApp(state)), $root);
const newVApp = createApp(state);
const patch = diff(vApp, newVApp);
$root = patch($root);
vApp = newVApp;
state.count += 1;
}, 1000);
97 changes: 97 additions & 0 deletions src/vdom/diff.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import zip from 'lodash.zip';
import spread from 'lodash.spread';
import render from './render';

export default function diff(oldVNode, newVNode) {
if (!newVNode) {
return function removeOldVNode($node) {
$node.remove();
return undefined;
};
}

// diff one or two strings
if (typeof oldVNode === 'string' || typeof newVNode === 'string') {
if (oldVNode !== newVNode) {
return function replaceOldVNode($node) {
const $newNode = render(newVNode);
$node.replaceWith($newNode);
return $newNode;
};
}
return function doNothing() {
return undefined;
};
}

// when two v nodes have different tag names, we assume they are different
if (oldVNode.tagName !== newVNode.tagName) {
return function replaceOldVNode($node) {
const $newNode = render(newVNode);
$node.replaceWith($newNode);
return $newNode;
};
}

const patchAttrs = diffAttrs(oldVNode.attrs, newVNode.attrs);
const patchChildren = diffChildren(oldVNode.children, newVNode.children);

return function patchOldVNode($node) {
patchAttrs($node);
patchChildren($node);
return $node;
};
}

function diffAttrs(oldAttrs, newAttrs) {
const setAttrsPatches = Object.keys(newAttrs)
.filter(attrName => !oldAttrs.hasOwnProperty(attrName))
.map(
attrName =>
function setAttr($node) {
$node.setAttribute(attrName, newAttrs[attrName]);
return $node;
}
);

const removeAttrPatches = Object.keys(oldAttrs)
.filter(oldAttrName => !newAttrs.hasOwnProperty(oldAttrName))
.map(
attrName =>
function removeOldVNodeAttr($node) {
$node.removeAttribute(attrName);
return $node;
}
);

const patches = removeAttrPatches.concat(setAttrsPatches);

return function patchOldVNodeAttrs($node) {
for (const patch of patches) {
patch($node);
}
return $node;
};
}

function diffChildren(oldVChildren, newVChildren) {
const childPatches = zip(oldVChildren, newVChildren).map(spread(diff));

const additionalPatches = newVChildren.slice(oldVChildren.length).map(
newChild =>
function appendNewVChild($node) {
$node.appendChild(render(newChild));
return $node;
}
);

return function patchChildren($parent) {
for (const [patch, child] of zip(childPatches, $parent.childNodes)) {
patch(child);
}
for (const patch of additionalPatches) {
patch($parent);
}
return $parent;
};
}
2 changes: 2 additions & 0 deletions src/vdom/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import _createElement from './createElement';
import _diff from './diff';
import _mount from './mount';
import _render from './render';

export const createElement = _createElement;
export const diff = _diff;
export const mount = _mount;
export const render = _render;

0 comments on commit b2fa5dc

Please sign in to comment.