diff --git a/packages/react-native-renderer/src/ReactNativeAttributePayload.js b/packages/react-native-renderer/src/ReactNativeAttributePayload.js index 5e6cd4d81533f..edb6668231a2a 100644 --- a/packages/react-native-renderer/src/ReactNativeAttributePayload.js +++ b/packages/react-native-renderer/src/ReactNativeAttributePayload.js @@ -38,7 +38,7 @@ function defaultDiffer(prevProp: mixed, nextProp: mixed): boolean { return true; } else { // For objects and arrays, the default diffing algorithm is a deep compare - return deepDiffer(prevProp, nextProp); + return deepDiffer(prevProp, nextProp, {unsafelyIgnoreFunctions: true}); } } diff --git a/packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/deepDiffer.js b/packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/deepDiffer.js index a4ede524658ce..8bc969ffb6523 100644 --- a/packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/deepDiffer.js +++ b/packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/deepDiffer.js @@ -3,20 +3,43 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. + * + * @format + * @flow */ 'use strict'; -// TODO: Move deepDiffer into react +type Options = {|+unsafelyIgnoreFunctions?: boolean|}; -const deepDiffer = function(one: any, two: any): boolean { +/* + * @returns {bool} true if different, false if equal + */ +const deepDiffer = function( + one: any, + two: any, + maxDepthOrOptions: Options | number = -1, + maybeOptions?: Options, +): boolean { + const options = + typeof maxDepthOrOptions === 'number' ? maybeOptions : maxDepthOrOptions; + const maxDepth = + typeof maxDepthOrOptions === 'number' ? maxDepthOrOptions : -1; + if (maxDepth === 0) { + return true; + } if (one === two) { // Short circuit on identical object references instead of traversing them. return false; } if (typeof one === 'function' && typeof two === 'function') { - // We consider all functions equal - return false; + // We consider all functions equal unless explicitly configured otherwise + let unsafelyIgnoreFunctions = + options == null ? null : options.unsafelyIgnoreFunctions; + if (unsafelyIgnoreFunctions == null) { + unsafelyIgnoreFunctions = true; + } + return !unsafelyIgnoreFunctions; } if (typeof one !== 'object' || one === null) { // Primitives can be directly compared @@ -37,13 +60,13 @@ const deepDiffer = function(one: any, two: any): boolean { return true; } for (let ii = 0; ii < len; ii++) { - if (deepDiffer(one[ii], two[ii])) { + if (deepDiffer(one[ii], two[ii], maxDepth - 1, options)) { return true; } } } else { for (const key in one) { - if (deepDiffer(one[key], two[key])) { + if (deepDiffer(one[key], two[key], maxDepth - 1, options)) { return true; } } diff --git a/packages/react-native-renderer/src/__tests__/ReactNativeAttributePayload-test.js b/packages/react-native-renderer/src/__tests__/ReactNativeAttributePayload-test.js index 49190d11da954..5e15208338860 100644 --- a/packages/react-native-renderer/src/__tests__/ReactNativeAttributePayload-test.js +++ b/packages/react-native-renderer/src/__tests__/ReactNativeAttributePayload-test.js @@ -231,4 +231,44 @@ describe('ReactNativeAttributePayload', () => { ), ).toEqual({a: null, c: true}); }); + + it('should skip changed functions', () => { + expect( + diff( + { + a: function() { + return 1; + }, + }, + { + a: function() { + return 9; + }, + }, + {a: true}, + ), + ).toEqual(null); + }); + + it('should skip deeply-nested changed functions', () => { + expect( + diff( + { + wrapper: { + a: function() { + return 1; + }, + }, + }, + { + wrapper: { + a: function() { + return 9; + }, + }, + }, + {wrapper: true}, + ), + ).toEqual(null); + }); }); diff --git a/scripts/flow/react-native-host-hooks.js b/scripts/flow/react-native-host-hooks.js index 4415dde1a2d88..460f6170cdd2f 100644 --- a/scripts/flow/react-native-host-hooks.js +++ b/scripts/flow/react-native-host-hooks.js @@ -19,8 +19,20 @@ import type { import type {RNTopLevelEventType} from 'legacy-events/TopLevelEventTypes'; import type {CapturedError} from 'react-reconciler/src/ReactCapturedValue'; +type DeepDifferOptions = {|+unsafelyIgnoreFunctions?: boolean|}; + declare module 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface' { - declare export function deepDiffer(one: any, two: any): boolean; + declare export function deepDiffer( + one: any, + two: any, + maxDepth?: number, + options?: DeepDifferOptions, + ): boolean; + declare export function deepDiffer( + one: any, + two: any, + options: DeepDifferOptions, + ): boolean; declare export function deepFreezeAndThrowOnMutationInDev(obj: T): T; declare export function flattenStyle(style: any): any; declare export var RCTEventEmitter: {