Last active
January 27, 2019 19:37
-
-
Save pmrt/9fa36a23ed1ea241ae33d4967e9f3f49 to your computer and use it in GitHub Desktop.
PropDiff
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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