Skip to content

Commit 61d3dd0

Browse files
authored
Update deepDiffer usage in React Native renderer (#17282)
* Add RN prop diffing test with function values * Update RN deepDiffer mock * Explicitly ignore functions in RN prop differ
1 parent e701632 commit 61d3dd0

File tree

4 files changed

+83
-8
lines changed

4 files changed

+83
-8
lines changed

packages/react-native-renderer/src/ReactNativeAttributePayload.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ function defaultDiffer(prevProp: mixed, nextProp: mixed): boolean {
3838
return true;
3939
} else {
4040
// For objects and arrays, the default diffing algorithm is a deep compare
41-
return deepDiffer(prevProp, nextProp);
41+
return deepDiffer(prevProp, nextProp, {unsafelyIgnoreFunctions: true});
4242
}
4343
}
4444

packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/deepDiffer.js

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,43 @@
33
*
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @format
8+
* @flow
69
*/
710

811
'use strict';
912

10-
// TODO: Move deepDiffer into react
13+
type Options = {|+unsafelyIgnoreFunctions?: boolean|};
1114

12-
const deepDiffer = function(one: any, two: any): boolean {
15+
/*
16+
* @returns {bool} true if different, false if equal
17+
*/
18+
const deepDiffer = function(
19+
one: any,
20+
two: any,
21+
maxDepthOrOptions: Options | number = -1,
22+
maybeOptions?: Options,
23+
): boolean {
24+
const options =
25+
typeof maxDepthOrOptions === 'number' ? maybeOptions : maxDepthOrOptions;
26+
const maxDepth =
27+
typeof maxDepthOrOptions === 'number' ? maxDepthOrOptions : -1;
28+
if (maxDepth === 0) {
29+
return true;
30+
}
1331
if (one === two) {
1432
// Short circuit on identical object references instead of traversing them.
1533
return false;
1634
}
1735
if (typeof one === 'function' && typeof two === 'function') {
18-
// We consider all functions equal
19-
return false;
36+
// We consider all functions equal unless explicitly configured otherwise
37+
let unsafelyIgnoreFunctions =
38+
options == null ? null : options.unsafelyIgnoreFunctions;
39+
if (unsafelyIgnoreFunctions == null) {
40+
unsafelyIgnoreFunctions = true;
41+
}
42+
return !unsafelyIgnoreFunctions;
2043
}
2144
if (typeof one !== 'object' || one === null) {
2245
// Primitives can be directly compared
@@ -37,13 +60,13 @@ const deepDiffer = function(one: any, two: any): boolean {
3760
return true;
3861
}
3962
for (let ii = 0; ii < len; ii++) {
40-
if (deepDiffer(one[ii], two[ii])) {
63+
if (deepDiffer(one[ii], two[ii], maxDepth - 1, options)) {
4164
return true;
4265
}
4366
}
4467
} else {
4568
for (const key in one) {
46-
if (deepDiffer(one[key], two[key])) {
69+
if (deepDiffer(one[key], two[key], maxDepth - 1, options)) {
4770
return true;
4871
}
4972
}

packages/react-native-renderer/src/__tests__/ReactNativeAttributePayload-test.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,4 +231,44 @@ describe('ReactNativeAttributePayload', () => {
231231
),
232232
).toEqual({a: null, c: true});
233233
});
234+
235+
it('should skip changed functions', () => {
236+
expect(
237+
diff(
238+
{
239+
a: function() {
240+
return 1;
241+
},
242+
},
243+
{
244+
a: function() {
245+
return 9;
246+
},
247+
},
248+
{a: true},
249+
),
250+
).toEqual(null);
251+
});
252+
253+
it('should skip deeply-nested changed functions', () => {
254+
expect(
255+
diff(
256+
{
257+
wrapper: {
258+
a: function() {
259+
return 1;
260+
},
261+
},
262+
},
263+
{
264+
wrapper: {
265+
a: function() {
266+
return 9;
267+
},
268+
},
269+
},
270+
{wrapper: true},
271+
),
272+
).toEqual(null);
273+
});
234274
});

scripts/flow/react-native-host-hooks.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,20 @@ import type {
1919
import type {RNTopLevelEventType} from 'legacy-events/TopLevelEventTypes';
2020
import type {CapturedError} from 'react-reconciler/src/ReactCapturedValue';
2121

22+
type DeepDifferOptions = {|+unsafelyIgnoreFunctions?: boolean|};
23+
2224
declare module 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface' {
23-
declare export function deepDiffer(one: any, two: any): boolean;
25+
declare export function deepDiffer(
26+
one: any,
27+
two: any,
28+
maxDepth?: number,
29+
options?: DeepDifferOptions,
30+
): boolean;
31+
declare export function deepDiffer(
32+
one: any,
33+
two: any,
34+
options: DeepDifferOptions,
35+
): boolean;
2436
declare export function deepFreezeAndThrowOnMutationInDev<T>(obj: T): T;
2537
declare export function flattenStyle(style: any): any;
2638
declare export var RCTEventEmitter: {

0 commit comments

Comments
 (0)