Skip to content

Commit b4fdc87

Browse files
authored
feat: completely rework package (#266)
Performance improvements and proper support for `TypedArray`s and `ArrayBuffer`s BREAKING CHANGE: package now exports single `isEqual` function that covers all functionality of previous functions.
1 parent f66b757 commit b4fdc87

9 files changed

+334
-648
lines changed

README.md

Lines changed: 65 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -9,103 +9,100 @@
99
![Codecov](https://img.shields.io/codecov/c/github/react-hookz/deep-equal?style=flat-square)
1010
![NPM Type Definitions](https://img.shields.io/npm/types/%40react-hookz%2Fdeep-equal?style=flat-square)
1111

12-
× **[DISCORD](https://discord.gg/Fjwphtu65f)**
13-
× **[CHANGELOG](https://github.com/react-hookz/deep-equal/blob/master/CHANGELOG.md)**
14-
×
12+
× **[DISCORD](https://discord.gg/Fjwphtu65f)** ×
1513

1614
</div>
1715

1816
---
1917

20-
Yet fastest deep comparator with ES6+ support.
18+
**The fastest deep comparator with full ES6+ support.**
2119

22-
## Install
20+
---
21+
22+
## Features 🚀
23+
24+
- ✅ Handles ES6+ structures like `Map`, `Set`, `TypedArray`, `DataView`, and `ArrayBuffer`.
25+
- ✅ Supports `Date`, `RegExp`, and `NaN`.
26+
- ✅ Safe handling of React and Preact objects (no stack overflow).
27+
- ✅ Works seamlessly with objects created via `Object.create(null)`.
28+
- ⚠️ **Circular reference handling**:
29+
- **Supported**: React and Preact objects.
30+
- **Not supported**: Other objects (causes stack overflow).
31+
32+
---
33+
34+
## Installation 📦
2335

24-
This one is pretty simple, everyone knows what to do:
36+
It's as simple as:
2537

26-
```shell
27-
npm i @react-hookz/deep-equal
38+
```bash
39+
npm install @react-hookz/deep-equal
2840
# or
2941
yarn add @react-hookz/deep-equal
3042
```
3143

32-
## Usage
44+
---
3345

34-
#### Importing
46+
## Usage 💡
3547

36-
> This package distributed with ESNext language level and ES modules system.
37-
> It means that depending on your browser target you might need to transpile it. Every major
38-
> bundler provides a way to transpile `node_modules` fully or partially.
39-
> Address your bundler documentation for more details.
48+
### Importing
4049

41-
```ts
42-
import { isEqual } from '@react-hookz/deep-equal';
43-
```
50+
This package is distributed using the ESNext language level and ES module system. Depending on your target environment, you might need to transpile the package. Check your bundler's documentation for instructions on transpiling `node_modules`.
4451

45-
#### Variants
52+
```typescript
53+
import { isEqual } from '@react-hookz/deep-equal';
4654

47-
This package provides 4 variants of comparator:
55+
isEqual({ a: 1 }, { a: 1 }); // true
56+
```
4857

49-
- **`isEqual`** - es6+ compatible, for cases when you expect almost any data on input.
50-
- **`isEqualReact`** - es6+ compatible but with extra checks for `React` and `Preact` objects (they
51-
contain circular references).
52-
- **`isEqualSimple`** - simplified version without support for `Map`, `Set`, `ArrayBuffer`,
53-
`TypedArray` and `DataView`.
54-
- **`isEqualSimpleReact`** - same as `isEqualSimple` but with checks for `React` and `Preact`
58+
---
5559

56-
## Performance
60+
## Performance
5761

58-
> **Note:** below tests are mage against certain dataset (can be found in benchmarks), that may or
59-
> may not be representative for your case and your data.
60-
> It is better to perform benchmarks against your datasets.
62+
**Note**: Benchmarks were conducted on specific datasets (available in the `benchmarks` directory). Your results may vary depending on your data and use case. Running benchmarks on your dataset is recommended.
6163

62-
**simple data (non-es6+)**
64+
### Simple Data (non-ES6+)
6365

6466
<pre>
65-
# mixed (equal)
66-
@react-hookz/deep-equal x 2,328,007 ops/sec ±0.33% (94 runs sampled)
67-
@react-hookz/deep-equal (react) x 2,248,935 ops/sec ±1.05% (92 runs sampled)
68-
@react-hookz/deep-equal (simple) x 2,502,281 ops/sec ±0.39% (97 runs sampled)
69-
@react-hookz/deep-equal (simple react) x 2,292,288 ops/sec ±0.91% (93 runs sampled)
70-
dequal x 1,884,722 ops/sec ±0.57% (92 runs sampled)
71-
dequal (lite) x 1,875,235 ops/sec ±0.32% (95 runs sampled)
72-
fast-deep-equal x 1,732,963 ops/sec ±0.66% (94 runs sampled)
73-
react-fast-compare x 1,640,019 ops/sec ±0.22% (96 runs sampled)
74-
Fastest is @react-hookz/deep-equal (simple)
75-
76-
# mixed (unequal)
77-
@react-hookz/deep-equal x 3,333,499 ops/sec ±0.54% (91 runs sampled)
78-
@react-hookz/deep-equal (react) x 3,175,146 ops/sec ±0.59% (94 runs sampled)
79-
@react-hookz/deep-equal (simple) x 3,236,086 ops/sec ±0.37% (92 runs sampled)
80-
@react-hookz/deep-equal (simple react) x 3,187,855 ops/sec ±0.48% (96 runs sampled)
81-
dequal x 1,110,380 ops/sec ±1.26% (89 runs sampled)
82-
dequal (lite) x 1,135,251 ops/sec ±1.01% (94 runs sampled)
83-
fast-deep-equal x 2,238,446 ops/sec ±0.50% (97 runs sampled)
84-
react-fast-compare x 2,221,893 ops/sec ±0.20% (93 runs sampled)
85-
Fastest is @react-hookz/deep-equal
67+
name hz min max mean p75 p99 p995 p999 rme samples
68+
· @react-hookz/deep-equal 1,780,770.64 0.0005 0.7278 0.0006 0.0006 0.0010 0.0011 0.0013 ±0.18% 1780771 fastest
69+
· react-fast-compare 1,690,244.66 0.0005 3.2804 0.0006 0.0006 0.0010 0.0012 0.0013 ±0.65% 1690245
70+
· fast-deep-equal 1,663,437.00 0.0005 3.5417 0.0006 0.0006 0.0010 0.0011 0.0012 ±0.70% 1663438 slowest
71+
· dequal 1,693,113.15 0.0005 3.2976 0.0006 0.0006 0.0007 0.0010 0.0012 ±0.65% 1693114
8672
</pre>
8773

88-
**complex data (with es6+)**
74+
### Complex Data (ES6+)
8975

9076
<pre>
91-
# mixed (equal)
92-
@react-hookz/deep-equal x 1,417,373 ops/sec ±0.54% (94 runs sampled)
93-
@react-hookz/deep-equal (react) x 1,350,950 ops/sec ±0.39% (89 runs sampled)
94-
dequal x 714,145 ops/sec ±0.43% (94 runs sampled)
95-
fast-deep-equal x 1,066,887 ops/sec ±0.20% (98 runs sampled)
96-
Fastest is @react-hookz/deep-equal
97-
98-
# mixed (unequal)
99-
@react-hookz/deep-equal x 2,096,641 ops/sec ±0.23% (98 runs sampled)
100-
@react-hookz/deep-equal (react) x 2,003,117 ops/sec ±0.56% (95 runs sampled)
101-
dequal x 570,606 ops/sec ±0.78% (93 runs sampled)
102-
fast-deep-equal x 2,149,295 ops/sec ±2.91% (80 runs sampled)
103-
Fastest is @react-hookz/deep-equal,fast-deep-equal
77+
name hz min max mean p75 p99 p995 p999 rme samples
78+
· @react-hookz/deep-equal 1,725,589.83 0.0005 0.2565 0.0006 0.0006 0.0009 0.0010 0.0012 ±0.20% 1725590 fastest
79+
· react-fast-compare 1,406,227.02 0.0006 0.1635 0.0007 0.0007 0.0011 0.0014 0.0016 ±0.21% 1406228
80+
· fast-deep-equal 1,553,848.07 0.0005 2.7540 0.0006 0.0007 0.0008 0.0011 0.0013 ±0.57% 1553849
81+
· dequal 1,026,213.59 0.0008 0.1810 0.0010 0.0010 0.0011 0.0012 0.0019 ±0.16% 1026214 slowest
10482
</pre>
10583

106-
Full benchmarks results can be found in the [`benchmark`](/benchmark) directory.
84+
---
85+
86+
## Run Benchmarks Locally 🛠️
87+
88+
To run the benchmarks on your machine:
89+
90+
1. Clone the repository:
91+
```bash
92+
git clone https://github.com/react-hookz/deep-equal
93+
cd deep-equal
94+
```
95+
2. Install dependencies:
96+
```bash
97+
corepack enable
98+
yarn
99+
```
100+
3. Run benchmarks:
101+
```bash
102+
yarn benchmark
103+
```
107104

108-
To run benchmarks simply clone this repo and make `yarn && yarn benchmark` in repo root.
105+
---
109106

110107
## Contributors
111108

@@ -139,4 +136,4 @@ To run benchmarks simply clone this repo and make `yarn && yarn benchmark` in re
139136
## Related projects
140137

141138
- [@react-hookz/web](https://github.com/react-hookz/web) - React hooks done right, for browser and
142-
SSR.
139+
SSR.

src/comparators.benchmark.ts

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,13 @@ describe('Array comparators', () => {
3232
const strictEqual = (a: any, b: any): boolean => a === b;
3333

3434
const whileLoop = (a: any[], b: any[], equal: (a: any, b: any) => boolean): boolean => {
35-
let l = a.length;
36-
if (l !== b.length) {
37-
return false;
35+
let {length} = a;
36+
if (length === b.length) {
37+
// eslint-disable-next-line curly
38+
while (length-- && equal(a[length], b[length])) ;
3839
}
3940

40-
// eslint-disable-next-line no-empty
41-
while (l-- && equal(a[l], b[l])) {}
42-
43-
return l === -1;
41+
return length === -1;
4442
};
4543

4644
const forLoop = (a: any[], b: any[], equal: (a: any, b: any) => boolean) => {
@@ -58,6 +56,20 @@ describe('Array comparators', () => {
5856
return true;
5957
};
6058

59+
const forof = (a: any[], b: any[], equal: (a: any, b: any) => boolean) => {
60+
if (a.length !== b.length) {
61+
return false;
62+
}
63+
64+
for (const [i, element] of a.entries()) {
65+
if (!equal(element, b[i])) {
66+
return false;
67+
}
68+
}
69+
70+
return true;
71+
};
72+
6173
const a = Array.from({length: 100}).fill(2);
6274
const b = Array.from({length: 100}).fill(2);
6375
const c = Array.from({length: 100}).fill(2);
@@ -76,6 +88,13 @@ describe('Array comparators', () => {
7688
bench('forLoop inequal', () => {
7789
forLoop(a, c, strictEqual);
7890
}, {time: 1000});
91+
92+
bench('forof equal', () => {
93+
forof(a, b, strictEqual);
94+
}, {time: 1000});
95+
bench('forof inequal', () => {
96+
forof(a, c, strictEqual);
97+
}, {time: 1000});
7998
});
8099

81100
/*
@@ -296,9 +315,13 @@ describe('Set comparators', () => {
296315
return true;
297316
};
298317

299-
const a = new Set(Array.from({length: 100}).fill(2));
300-
const b = new Set(Array.from({length: 100}).fill(2));
301-
const c = new Set([...Array.from({length: 49}).fill(2), 1, Array.from({length: 50}).fill(2)]);
318+
const a = new Set(Array.from({length: 100}, (_v, idx) => idx));
319+
const b = new Set(Array.from({length: 100}, (_v, idx) => idx));
320+
const c = new Set([
321+
...Array.from({length: 49}, (_v, idx) => idx),
322+
1,
323+
...Array.from({length: 50}, (_v, idx) => idx + 50),
324+
]);
302325

303326
bench('difference equal', () => {
304327
difference(a, b);

0 commit comments

Comments
 (0)