Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions packages/core/src/bundle/helpers/deepEqual/deepEqual.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
const isObject = (value) => typeof value === 'object' && value !== null;
const isPlainObject = (value) => {
const prototype = Object.getPrototypeOf(value);
return prototype === Object.prototype || prototype === null;
};
/**
* @name deepEqual
* @description - Performs a deep comparison between two values to determine if they are equivalent
* @category Helpers
* @usage low
*
* @param {any} a The first value to compare
* @param {any} b The second value to compare
* @returns {boolean} `true` if the two values are deeply equal, `false` otherwise
*
* @warning - Set members and Map keys are compared by reference, Map values are compared deeply. Objects without own enumerable string keys are compared by reference unless they are plain objects.
*
* @example
* deepEqual({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 2 } }); // true
*/
export const deepEqual = (a, b) => {
const seen = new WeakMap();
const mark = (x, y) => {
const visited = seen.get(x);
if (visited) {
if (visited.has(y)) return true;
visited.add(y);
return false;
}
seen.set(x, new WeakSet([y]));
return false;
};
const compare = (x, y) => {
if (Object.is(x, y)) return true;
if (!isObject(x) || !isObject(y)) return false;
if (Object.getPrototypeOf(x) !== Object.getPrototypeOf(y)) return false;
if (mark(x, y)) return true;
if (x instanceof Date) return y instanceof Date && Object.is(x.getTime(), y.getTime());
if (x instanceof RegExp)
return y instanceof RegExp && x.source === y.source && x.flags === y.flags;
if (x instanceof Error)
return y instanceof Error && x.name === y.name && x.message === y.message;
if (Array.isArray(x)) {
if (!Array.isArray(y) || x.length !== y.length) return false;
for (let index = 0; index < x.length; index += 1) {
if (!compare(x[index], y[index])) return false;
}
return true;
}
if (x instanceof Set) {
if (!(y instanceof Set) || x.size !== y.size) return false;
for (const value of x) {
if (!y.has(value)) return false;
}
return true;
}
if (x instanceof Map) {
if (!(y instanceof Map) || x.size !== y.size) return false;
for (const [key, value] of x) {
if (!y.has(key) || !compare(value, y.get(key))) return false;
}
return true;
}
const xo = x;
const yo = y;
const keysX = [...Object.keys(xo), ...Object.getOwnPropertySymbols(xo)];
const keysY = [...Object.keys(yo), ...Object.getOwnPropertySymbols(yo)];
if (keysX.length !== keysY.length) return false;
if (!Object.keys(xo).length && !isPlainObject(x)) return false;
for (const key of keysX) {
if (!Object.hasOwn(yo, key)) return false;
if (!compare(xo[key], yo[key])) return false;
}
return true;
};
return compare(a, b);
};
2 changes: 2 additions & 0 deletions packages/core/src/bundle/helpers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ export * from './createEventEmitter/createEventEmitter';
export * from './createReactiveContext/createReactiveContext';
export * from './createSharedHook/createSharedHook';
export * from './createStore/createStore';
export * from './deepEqual/deepEqual';
export * from './makeDestructurable/makeDestructurable';
export * from './shallowEqual/shallowEqual';
61 changes: 61 additions & 0 deletions packages/core/src/bundle/helpers/shallowEqual/shallowEqual.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
const isObject = (value) => typeof value === 'object' && value !== null;
const isPlainObject = (value) => {
const prototype = Object.getPrototypeOf(value);
return prototype === Object.prototype || prototype === null;
};
/**
* @name shallowEqual
* @description - Performs a shallow comparison between two values to determine if they are equivalent
* @category Helpers
* @usage low
*
* @param {any} a The first value to compare
* @param {any} b The second value to compare
* @returns {boolean} `true` if the two values are shallowly equal, `false` otherwise
*
* @warning - Set members and Map keys are compared by reference. Objects without own enumerable string keys are compared by reference unless they are plain objects.
*
* @example
* shallowEqual({ a: 1, b: 2 }, { a: 1, b: 2 }); // true
*/
export const shallowEqual = (a, b) => {
if (Object.is(a, b)) return true;
if (!isObject(a) || !isObject(b)) return false;
if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) return false;
if (a instanceof Date) return b instanceof Date && Object.is(a.getTime(), b.getTime());
if (a instanceof RegExp)
return b instanceof RegExp && a.source === b.source && a.flags === b.flags;
if (a instanceof Error) return b instanceof Error && a.name === b.name && a.message === b.message;
if (Array.isArray(a)) {
if (!Array.isArray(b) || a.length !== b.length) return false;
for (let index = 0; index < a.length; index += 1) {
if (!Object.is(a[index], b[index])) return false;
}
return true;
}
if (a instanceof Set) {
if (!(b instanceof Set) || a.size !== b.size) return false;
for (const value of a) {
if (!b.has(value)) return false;
}
return true;
}
if (a instanceof Map) {
if (!(b instanceof Map) || a.size !== b.size) return false;
for (const [key, value] of a) {
if (!b.has(key) || !Object.is(value, b.get(key))) return false;
}
return true;
}
const ao = a;
const bo = b;
const keysA = [...Object.keys(ao), ...Object.getOwnPropertySymbols(ao)];
const keysB = [...Object.keys(bo), ...Object.getOwnPropertySymbols(bo)];
if (keysA.length !== keysB.length) return false;
if (!Object.keys(ao).length && !isPlainObject(a)) return false;
for (const key of keysA) {
if (!Object.hasOwn(bo, key)) return false;
if (!Object.is(ao[key], bo[key])) return false;
}
return true;
};
2 changes: 1 addition & 1 deletion packages/core/src/bundle/hooks/lifecycle.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export * from './useAsyncEffect/useAsyncEffect';
export * from './useCompareEffect/useCompareEffect';
export * from './useDidUpdate/useDidUpdate';
export * from './useIsFirstRender/useIsFirstRender';
export * from './useIsomorphicLayoutEffect/useIsomorphicLayoutEffect';
export * from './useMount/useMount';
export * from './useShallowEffect/useShallowEffect';
export * from './useUnmount/useUnmount';
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useEffect, useRef } from 'react';
import { deepEqual } from '@/helpers/deepEqual/deepEqual';
/**
* @name useCompareEffect
* @description - Hook that executes an effect only when the compare function reports a dependency as changed
* @category Lifecycle
* @usage low
*
* @param {EffectCallback} effect The effect callback
* @param {DependencyList} [deps] The dependencies list for the effect
* @param {CompareFunction} [compare=deepEqual] The function that returns `true` when a dependency is equal to its previous value
*
* @warning - The compare function is called for each dependency separately, not for the whole list
*
* @example
* useCompareEffect(() => console.log("effect"), [user]);
*
* @example
* useCompareEffect(() => console.log("effect"), [user], shallowEqual);
*
* @example
* useCompareEffect(() => console.log("effect"), [user], (user, prevUser) => user.id === prevUser.id);
*/
export const useCompareEffect = (effect, deps, compare = deepEqual) => {
const depsRef = useRef(undefined);
const signalRef = useRef(0);
const prevDeps = depsRef.current;
const equal =
!!deps &&
!!prevDeps &&
deps.length === prevDeps.length &&
deps.every((dep, index) => compare(dep, prevDeps[index]));
if (!equal) signalRef.current += 1;
depsRef.current = deps;
useEffect(effect, [signalRef.current]);
};

This file was deleted.

Loading