mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Back out "Animated: Optimize onUserDrivenAnimationEnded Deopt" (#48733)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/48733 Original commit changeset: e7d7e486bbfd Original Phabricator Diff: D67872307 changelog: [internal] Reviewed By: yungsters Differential Revision: D68268701 fbshipit-source-id: 1eb0dcb257c2d568cbc02ab2aa4a16b161797b24
This commit is contained in:
committed by
Facebook GitHub Bot
parent
67fc85615a
commit
6fa4cc6085
@@ -68,15 +68,22 @@ describe('AnimatedValue', () => {
|
||||
expect(callback).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('creates a native node on attach', () => {
|
||||
it('creates a native node when adding a listener', () => {
|
||||
const node = createNativeAnimatedValue();
|
||||
node.__attach();
|
||||
expect(NativeAnimatedHelper.API.createAnimatedNode).not.toBeCalled();
|
||||
|
||||
const id = node.addListener(jest.fn());
|
||||
node.removeListener(id);
|
||||
expect(NativeAnimatedHelper.API.createAnimatedNode).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('drops a created native node on detach', () => {
|
||||
const node = createNativeAnimatedValue();
|
||||
node.__attach();
|
||||
expect(NativeAnimatedHelper.API.createAnimatedNode).toBeCalledTimes(0);
|
||||
|
||||
node.addListener(jest.fn());
|
||||
expect(NativeAnimatedHelper.API.createAnimatedNode).toBeCalledTimes(1);
|
||||
expect(NativeAnimatedHelper.API.dropAnimatedNode).toBeCalledTimes(0);
|
||||
|
||||
|
||||
+35
-13
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
|
||||
import type {EventSubscription} from '../../vendor/emitter/EventEmitter';
|
||||
import type {PlatformConfig} from '../AnimatedPlatformConfig';
|
||||
import type Animation, {EndCallback} from '../animations/Animation';
|
||||
import type {InterpolationConfigType} from './AnimatedInterpolation';
|
||||
import type AnimatedNode from './AnimatedNode';
|
||||
@@ -84,6 +85,7 @@ function _executeAsAnimatedBatch(id: string, operation: () => void) {
|
||||
* See https://reactnative.dev/docs/animatedvalue
|
||||
*/
|
||||
export default class AnimatedValue extends AnimatedWithChildren {
|
||||
#listenerCount: number = 0;
|
||||
#updateSubscription: ?EventSubscription = null;
|
||||
|
||||
_value: number;
|
||||
@@ -105,20 +107,8 @@ export default class AnimatedValue extends AnimatedWithChildren {
|
||||
}
|
||||
}
|
||||
|
||||
__attach(): void {
|
||||
__detach() {
|
||||
if (this.__isNative) {
|
||||
// NOTE: In theory, we should only need to call this when any listeners
|
||||
// are added. However, there is a global `onUserDrivenAnimationEnded`
|
||||
// listener that relies on `onAnimatedValueUpdate` having fired to update
|
||||
// the values in JavaScript. If that listener is removed, this could be
|
||||
// re-optimized.
|
||||
this.#ensureUpdateSubscriptionExists();
|
||||
}
|
||||
}
|
||||
|
||||
__detach(): void {
|
||||
if (this.__isNative) {
|
||||
this.#updateSubscription?.remove();
|
||||
NativeAnimatedAPI.getValue(this.__getNativeTag(), value => {
|
||||
this._value = value - this._offset;
|
||||
});
|
||||
@@ -131,6 +121,38 @@ export default class AnimatedValue extends AnimatedWithChildren {
|
||||
return this._value + this._offset;
|
||||
}
|
||||
|
||||
__makeNative(platformConfig: ?PlatformConfig): void {
|
||||
super.__makeNative(platformConfig);
|
||||
if (this.#listenerCount > 0) {
|
||||
this.#ensureUpdateSubscriptionExists();
|
||||
}
|
||||
}
|
||||
|
||||
addListener(callback: (value: any) => mixed): string {
|
||||
const id = super.addListener(callback);
|
||||
this.#listenerCount++;
|
||||
if (this.__isNative) {
|
||||
this.#ensureUpdateSubscriptionExists();
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
removeListener(id: string): void {
|
||||
super.removeListener(id);
|
||||
this.#listenerCount--;
|
||||
if (this.__isNative && this.#listenerCount === 0) {
|
||||
this.#updateSubscription?.remove();
|
||||
}
|
||||
}
|
||||
|
||||
removeAllListeners(): void {
|
||||
super.removeAllListeners();
|
||||
this.#listenerCount = 0;
|
||||
if (this.__isNative) {
|
||||
this.#updateSubscription?.remove();
|
||||
}
|
||||
}
|
||||
|
||||
#ensureUpdateSubscriptionExists(): void {
|
||||
if (this.#updateSubscription != null) {
|
||||
return;
|
||||
|
||||
@@ -17,7 +17,9 @@ import * as ReactNativeFeatureFlags from '../../src/private/featureflags/ReactNa
|
||||
import {isPublicInstance as isFabricPublicInstance} from '../ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstanceUtils';
|
||||
import useRefEffect from '../Utilities/useRefEffect';
|
||||
import {AnimatedEvent} from './AnimatedEvent';
|
||||
import AnimatedNode from './nodes/AnimatedNode';
|
||||
import AnimatedProps from './nodes/AnimatedProps';
|
||||
import AnimatedValue from './nodes/AnimatedValue';
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
@@ -37,6 +39,11 @@ type CallbackRef<T> = T => mixed;
|
||||
|
||||
type UpdateCallback = () => void;
|
||||
|
||||
type AnimatedValueListeners = Array<{
|
||||
propValue: AnimatedValue,
|
||||
listenerId: string,
|
||||
}>;
|
||||
|
||||
const useMemoOrAnimatedPropsMemo =
|
||||
ReactNativeFeatureFlags.enableAnimatedPropsMemo()
|
||||
? useAnimatedPropsMemo
|
||||
@@ -162,6 +169,7 @@ export default function useAnimatedProps<TProps: {...}, TInstance>(
|
||||
|
||||
const target = getEventTarget(instance);
|
||||
const events = [];
|
||||
const animatedValueListeners: AnimatedValueListeners = [];
|
||||
|
||||
for (const propName in props) {
|
||||
// $FlowFixMe[invalid-computed-prop]
|
||||
@@ -169,6 +177,8 @@ export default function useAnimatedProps<TProps: {...}, TInstance>(
|
||||
if (propValue instanceof AnimatedEvent && propValue.__isNative) {
|
||||
propValue.__attach(target, propName);
|
||||
events.push([propName, propValue]);
|
||||
// $FlowFixMe[incompatible-call] - the `addListenersToPropsValue` drills down the propValue.
|
||||
addListenersToPropsValue(propValue, animatedValueListeners);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,6 +188,10 @@ export default function useAnimatedProps<TProps: {...}, TInstance>(
|
||||
for (const [propName, propValue] of events) {
|
||||
propValue.__detach(target, propName);
|
||||
}
|
||||
|
||||
for (const {propValue, listenerId} of animatedValueListeners) {
|
||||
propValue.removeListener(listenerId);
|
||||
}
|
||||
};
|
||||
},
|
||||
[node, useNativePropsInFabric, props],
|
||||
@@ -201,6 +215,35 @@ function reduceAnimatedProps<TProps>(
|
||||
};
|
||||
}
|
||||
|
||||
function addListenersToPropsValue(
|
||||
propValue: AnimatedValue,
|
||||
accumulator: AnimatedValueListeners,
|
||||
) {
|
||||
// propValue can be a scalar value, an array or an object.
|
||||
if (propValue instanceof AnimatedValue) {
|
||||
const listenerId = propValue.addListener(() => {});
|
||||
accumulator.push({propValue, listenerId});
|
||||
} else if (Array.isArray(propValue)) {
|
||||
// An array can be an array of scalar values, arrays of arrays, or arrays of objects
|
||||
for (const prop of propValue) {
|
||||
addListenersToPropsValue(prop, accumulator);
|
||||
}
|
||||
} else if (propValue instanceof Object) {
|
||||
addAnimatedValuesListenersToProps(propValue, accumulator);
|
||||
}
|
||||
}
|
||||
|
||||
function addAnimatedValuesListenersToProps(
|
||||
props: AnimatedNode,
|
||||
accumulator: AnimatedValueListeners,
|
||||
) {
|
||||
for (const propName in props) {
|
||||
// $FlowFixMe[prop-missing] - This is an object contained in a prop, but we don't know the exact type.
|
||||
const propValue = props[propName];
|
||||
addListenersToPropsValue(propValue, accumulator);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages the lifecycle of the supplied `AnimatedProps` by invoking `__attach`
|
||||
* and `__detach`. However, this is more complicated because `AnimatedProps`
|
||||
|
||||
@@ -1106,9 +1106,12 @@ declare export default class AnimatedValue extends AnimatedWithChildren {
|
||||
_animation: ?Animation;
|
||||
_tracking: ?AnimatedTracking;
|
||||
constructor(value: number, config?: ?AnimatedValueConfig): void;
|
||||
__attach(): void;
|
||||
__detach(): void;
|
||||
__getValue(): number;
|
||||
__makeNative(platformConfig: ?PlatformConfig): void;
|
||||
addListener(callback: (value: any) => mixed): string;
|
||||
removeListener(id: string): void;
|
||||
removeAllListeners(): void;
|
||||
setValue(value: number): void;
|
||||
setOffset(offset: number): void;
|
||||
flattenOffset(): void;
|
||||
|
||||
+9
-29
@@ -208,21 +208,15 @@ describe('Native Animated', () => {
|
||||
|
||||
const value1 = new Animated.Value(0);
|
||||
value1.__makeNative();
|
||||
const nativeTag = value1.__getNativeTag();
|
||||
|
||||
value1.__attach();
|
||||
const listener = jest.fn();
|
||||
const id = value1.addListener(listener);
|
||||
expect(
|
||||
NativeAnimatedModule.startListeningToAnimatedNodeValue,
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
NativeAnimatedModule.startListeningToAnimatedNodeValue,
|
||||
).toHaveBeenCalledWith(nativeTag);
|
||||
).toHaveBeenCalledWith(value1.__getNativeTag());
|
||||
|
||||
NativeAnimatedHelper.nativeEventEmitter.emit('onAnimatedValueUpdate', {
|
||||
value: 42,
|
||||
tag: nativeTag,
|
||||
tag: value1.__getNativeTag(),
|
||||
});
|
||||
expect(listener).toHaveBeenCalledTimes(1);
|
||||
expect(listener).toBeCalledWith({value: 42});
|
||||
@@ -230,24 +224,20 @@ describe('Native Animated', () => {
|
||||
|
||||
NativeAnimatedHelper.nativeEventEmitter.emit('onAnimatedValueUpdate', {
|
||||
value: 7,
|
||||
tag: nativeTag,
|
||||
tag: value1.__getNativeTag(),
|
||||
});
|
||||
expect(listener).toHaveBeenCalledTimes(2);
|
||||
expect(listener).toBeCalledWith({value: 7});
|
||||
expect(value1.__getValue()).toBe(7);
|
||||
|
||||
value1.removeListener(id);
|
||||
value1.__detach();
|
||||
expect(
|
||||
NativeAnimatedModule.stopListeningToAnimatedNodeValue,
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
NativeAnimatedModule.stopListeningToAnimatedNodeValue,
|
||||
).toHaveBeenCalledWith(nativeTag);
|
||||
).toHaveBeenCalledWith(value1.__getNativeTag());
|
||||
|
||||
NativeAnimatedHelper.nativeEventEmitter.emit('onAnimatedValueUpdate', {
|
||||
value: 1492,
|
||||
tag: nativeTag,
|
||||
tag: value1.__getNativeTag(),
|
||||
});
|
||||
expect(listener).toHaveBeenCalledTimes(2);
|
||||
expect(value1.__getValue()).toBe(7);
|
||||
@@ -258,37 +248,27 @@ describe('Native Animated', () => {
|
||||
|
||||
const value1 = new Animated.Value(0);
|
||||
value1.__makeNative();
|
||||
const nativeTag = value1.__getNativeTag();
|
||||
|
||||
value1.__attach();
|
||||
const listener = jest.fn();
|
||||
[1, 2, 3, 4].forEach(() => value1.addListener(listener));
|
||||
expect(
|
||||
NativeAnimatedModule.startListeningToAnimatedNodeValue,
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
NativeAnimatedModule.startListeningToAnimatedNodeValue,
|
||||
).toHaveBeenCalledWith(nativeTag);
|
||||
).toHaveBeenCalledWith(value1.__getNativeTag());
|
||||
|
||||
NativeAnimatedHelper.nativeEventEmitter.emit('onAnimatedValueUpdate', {
|
||||
value: 42,
|
||||
tag: nativeTag,
|
||||
tag: value1.__getNativeTag(),
|
||||
});
|
||||
expect(listener).toHaveBeenCalledTimes(4);
|
||||
expect(listener).toBeCalledWith({value: 42});
|
||||
|
||||
value1.removeAllListeners();
|
||||
value1.__detach();
|
||||
expect(
|
||||
NativeAnimatedModule.stopListeningToAnimatedNodeValue,
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
NativeAnimatedModule.stopListeningToAnimatedNodeValue,
|
||||
).toHaveBeenCalledWith(nativeTag);
|
||||
).toHaveBeenCalledWith(value1.__getNativeTag());
|
||||
|
||||
NativeAnimatedHelper.nativeEventEmitter.emit('onAnimatedValueUpdate', {
|
||||
value: 7,
|
||||
tag: nativeTag,
|
||||
tag: value1.__getNativeTag(),
|
||||
});
|
||||
expect(listener).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user