Skip to content

Instantly share code, notes, and snippets.

@pmrt
Last active January 27, 2019 19:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pmrt/9fa36a23ed1ea241ae33d4967e9f3f49 to your computer and use it in GitHub Desktop.
Save pmrt/9fa36a23ed1ea241ae33d4967e9f3f49 to your computer and use it in GitHub Desktop.
PropDiff
function isObject(exp) {
return typeof exp === 'object';
}
function isArray(exp) {
return Array.isArray(exp);
}
function isEqual(exp, older, newer) {
if (typeof older !== typeof newer) {
return false;
}
// Exp is an array: e.g.: ['width']
//
// It'll turn the array into isEqual calls:
// isEqual(
// 'width',
// <older width value>:2,
// <newer width value>:2
// );
if (isArray(exp)) {
const isArr = isArray(older);
for (let i = 0; i < exp.length; i++) {
const el = exp[i];
// When older and newer are arrays
if (isArr) {
if (older.length !== newer.length) {
return false;
}
for (let j = 0; j < older.length; j++) {
if (!isEqual([el], older[j], newer[j])) {
return false;
}
}
// When older and newer are objects
} else if (!isEqual(el, older[el], newer[el])) {
return false;
}
}
return true;
}
// Exp is an object: e.g.: { radius: ['inner'] }.
//
// It'll turn the obj into isEqual calls:
// isEqual(
// ['inner'],
// <radius older value>:{inner: 3, value:3},
// <radius newer value>):{inner: 3, value:3}
// );
if (isObject(exp)) {
const keys = Object.keys(exp);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const val = exp[key];
if (!isEqual(val, older[key], newer[key])) {
return false;
}
}
return true;
}
// When an `older` prop is not primitive (that is, older or/and newer deep is > 1)
//
// e.g.: We're diffing a top older = { width: 10, height: 20, radius: { inner: { value: 5 } } }
// and our exp = ['radius'].
//
// — When we go deeper we find out that the radius = { inner: { value: 5 } } is not a primitive.
// So we turn this obj into isEqual calls:
// isEqual(
// ['inner'],
// { inner: { value: 5 } },
// { inner: { value: 5 } }
// );
//
// This code is only reachable if the current exp is a primitive (e.g.: 'radius') and the top exp
// wanted to check the whole object (e.g.: ['radius']). If the top exp is sth more specific like:
// { radius: { inner: ['value'] } }, isObject(exp) will take care of it.
// That's why it's safe to assume that if both:
// Object.keys(older).length and Object.keys(newer).length are not the same, the object has
// changed. If exp were more specific we would only want to check if the given prop (e.g.:
// `value` and not another one) has changed, comparing lengths wouldn't be safe as it'll
// return changed = true even if another prop is inserted/removed and `value` doesn't
// changed.
if (isObject(older)) {
const olderKeys = Object.keys(older);
if (olderKeys.length !== Object.keys(newer).length) {
return false;
}
return isEqual(olderKeys, older, newer);
}
// When a primitive values are not the same (update op.)
if (older !== newer) {
return false;
}
return true;
}
export class PropDiff {
constructor(older, newer) {
this.older = older;
this.newer = newer;
}
changed(exp) {
return !isEqual(exp, this.older, this.newer);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment