Animated: Feature Flags Cleanup (#48509)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/48509

Cleans up the following feature flags from `Animated`:

- `enableAnimatedAllowlist`
- `enableAnimatedPropsMemo`
- `useInsertionEffectsForAnimations`

This will significantly simplify some future planned work here (e.g. T209740497).

Changelog:
[Internal]

Reviewed By: javache

Differential Revision: D67867115

fbshipit-source-id: adf35c70d95f42c240342fda3b4f2e9b4bdfe30a
This commit is contained in:
Tim Yung
2025-01-22 08:54:58 -08:00
committed by Facebook GitHub Bot
parent 0883207e44
commit ab77bdf471
5 changed files with 6 additions and 169 deletions
@@ -24,8 +24,6 @@ import {
useCallback,
useEffect,
useInsertionEffect,
useLayoutEffect,
useMemo,
useReducer,
useRef,
} from 'react';
@@ -44,11 +42,6 @@ type AnimatedValueListeners = Array<{
listenerId: string,
}>;
const useMemoOrAnimatedPropsMemo =
ReactNativeFeatureFlags.enableAnimatedPropsMemo()
? useAnimatedPropsMemo
: useMemo;
export default function useAnimatedProps<TProps: {...}, TInstance>(
props: TProps,
allowlist?: ?AnimatedPropsAllowlist,
@@ -57,28 +50,14 @@ export default function useAnimatedProps<TProps: {...}, TInstance>(
const onUpdateRef = useRef<UpdateCallback | null>(null);
const timerRef = useRef<TimeoutID | null>(null);
const allowlistIfEnabled = ReactNativeFeatureFlags.enableAnimatedAllowlist()
? allowlist
: null;
const node = useMemoOrAnimatedPropsMemo(
() =>
new AnimatedProps(
props,
() => onUpdateRef.current?.(),
allowlistIfEnabled,
),
[allowlistIfEnabled, props],
const node = useAnimatedPropsMemo(
() => new AnimatedProps(props, () => onUpdateRef.current?.(), allowlist),
[allowlist, props],
);
const useNativePropsInFabric =
ReactNativeFeatureFlags.shouldUseSetNativePropsInFabric();
const useAnimatedPropsLifecycle =
ReactNativeFeatureFlags.useInsertionEffectsForAnimations()
? useAnimatedPropsLifecycle_insertionEffects
: useAnimatedPropsLifecycle_layoutEffects;
useAnimatedPropsLifecycle(node);
// TODO: This "effect" does three things:
@@ -208,9 +187,7 @@ function reduceAnimatedProps<TProps>(
// Force `collapsable` to be false so that the native view is not flattened.
// Flattened views cannot be accurately referenced by the native driver.
return {
...(ReactNativeFeatureFlags.enableAnimatedPropsMemo()
? node.__getValueWithStaticProps(props)
: node.__getValue()),
...node.__getValueWithStaticProps(props),
collapsable: false,
};
}
@@ -251,66 +228,7 @@ function addAnimatedValuesListenersToProps(
* nodes. So in order to optimize this, we avoid detaching until the next attach
* unless we are unmounting.
*/
function useAnimatedPropsLifecycle_layoutEffects(node: AnimatedProps): void {
const prevNodeRef = useRef<?AnimatedProps>(null);
const isUnmountingRef = useRef<boolean>(false);
useEffect(() => {
// It is ok for multiple components to call `flushQueue` because it noops
// if the queue is empty. When multiple animated components are mounted at
// the same time. Only first component flushes the queue and the others will noop.
NativeAnimatedHelper.API.flushQueue();
let drivenAnimationEndedListener: ?EventSubscription = null;
if (node.__isNative) {
drivenAnimationEndedListener =
NativeAnimatedHelper.nativeEventEmitter.addListener(
'onUserDrivenAnimationEnded',
data => {
node.update();
},
);
}
return () => {
drivenAnimationEndedListener?.remove();
};
});
useLayoutEffect(() => {
isUnmountingRef.current = false;
return () => {
isUnmountingRef.current = true;
};
}, []);
useLayoutEffect(() => {
node.__attach();
if (prevNodeRef.current != null) {
const prevNode = prevNodeRef.current;
// TODO: Stop restoring default values (unless `reset` is called).
prevNode.__restoreDefaultValues();
prevNode.__detach();
prevNodeRef.current = null;
}
return () => {
if (isUnmountingRef.current) {
// NOTE: Do not restore default values on unmount, see D18197735.
node.__detach();
} else {
prevNodeRef.current = node;
}
};
}, [node]);
}
/**
* Manages the lifecycle of the supplied `AnimatedProps` by invoking `__attach`
* and `__detach`. However, this is more complicated because `AnimatedProps`
* uses reference counting to determine when to recursively detach its children
* nodes. So in order to optimize this, we avoid detaching until the next attach
* unless we are unmounting.
*/
function useAnimatedPropsLifecycle_insertionEffects(node: AnimatedProps): void {
function useAnimatedPropsLifecycle(node: AnimatedProps): void {
const prevNodeRef = useRef<?AnimatedProps>(null);
const isUnmountingRef = useRef<boolean>(false);
@@ -9856,16 +9856,13 @@ exports[`public API should not change unintentionally src/private/featureflags/R
animatedShouldUseSingleOp: Getter<boolean>,
disableInteractionManager: Getter<boolean>,
enableAccessToHostTreeInFabric: Getter<boolean>,
enableAnimatedAllowlist: Getter<boolean>,
enableAnimatedClearImmediateFix: Getter<boolean>,
enableAnimatedPropsMemo: Getter<boolean>,
fixVirtualizeListCollapseWindowSize: Getter<boolean>,
isLayoutAnimationEnabled: Getter<boolean>,
shouldSkipStateUpdatesForLoopingAnimations: Getter<boolean>,
shouldUseAnimatedObjectForTransform: Getter<boolean>,
shouldUseRemoveClippedSubviewsAsDefaultOnIOS: Getter<boolean>,
shouldUseSetNativePropsInFabric: Getter<boolean>,
useInsertionEffectsForAnimations: Getter<boolean>,
useRefsForTextInputState: Getter<boolean>,
}>;
export type ReactNativeFeatureFlagsJsOnlyOverrides =
@@ -9925,16 +9922,13 @@ declare export const animatedShouldDebounceQueueFlush: Getter<boolean>;
declare export const animatedShouldUseSingleOp: Getter<boolean>;
declare export const disableInteractionManager: Getter<boolean>;
declare export const enableAccessToHostTreeInFabric: Getter<boolean>;
declare export const enableAnimatedAllowlist: Getter<boolean>;
declare export const enableAnimatedClearImmediateFix: Getter<boolean>;
declare export const enableAnimatedPropsMemo: Getter<boolean>;
declare export const fixVirtualizeListCollapseWindowSize: Getter<boolean>;
declare export const isLayoutAnimationEnabled: Getter<boolean>;
declare export const shouldSkipStateUpdatesForLoopingAnimations: Getter<boolean>;
declare export const shouldUseAnimatedObjectForTransform: Getter<boolean>;
declare export const shouldUseRemoveClippedSubviewsAsDefaultOnIOS: Getter<boolean>;
declare export const shouldUseSetNativePropsInFabric: Getter<boolean>;
declare export const useInsertionEffectsForAnimations: Getter<boolean>;
declare export const useRefsForTextInputState: Getter<boolean>;
declare export const commonTestFlag: Getter<boolean>;
declare export const commonTestFlagWithoutNativeImplementation: Getter<boolean>;
@@ -529,15 +529,6 @@ const definitions: FeatureFlagDefinitions = {
purpose: 'release',
},
},
enableAnimatedAllowlist: {
defaultValue: true,
metadata: {
description:
'Enables Animated to skip non-allowlisted props and styles.',
expectedReleaseValue: true,
purpose: 'release',
},
},
enableAnimatedClearImmediateFix: {
defaultValue: true,
metadata: {
@@ -548,15 +539,6 @@ const definitions: FeatureFlagDefinitions = {
purpose: 'experimentation',
},
},
enableAnimatedPropsMemo: {
defaultValue: true,
metadata: {
description:
'Enables Animated to analyze props to minimize invalidating `AnimatedProps`.',
expectedReleaseValue: true,
purpose: 'release',
},
},
fixVirtualizeListCollapseWindowSize: {
defaultValue: false,
metadata: {
@@ -615,15 +597,6 @@ const definitions: FeatureFlagDefinitions = {
purpose: 'experimentation',
},
},
useInsertionEffectsForAnimations: {
defaultValue: true,
metadata: {
description:
'Changes construction of the animation graph to `useInsertionEffect` instead of `useLayoutEffect`.',
expectedReleaseValue: true,
purpose: 'release',
},
},
useRefsForTextInputState: {
defaultValue: false,
metadata: {
@@ -1362,9 +1362,6 @@ describe('Native Animated', () => {
describe('Animated Components', () => {
it('preserves current values on update and unmount', async () => {
const {ReactNativeFeatureFlags} = importModules();
ReactNativeFeatureFlags.override({enableAnimatedPropsMemo: () => true});
const {Animated} = importModules();
const opacity = new Animated.Value(0);
@@ -1382,9 +1379,6 @@ describe('Native Animated', () => {
});
it('restores defaults when receiving new animated values', async () => {
const {ReactNativeFeatureFlags} = importModules();
ReactNativeFeatureFlags.override({enableAnimatedPropsMemo: () => true});
const {Animated} = importModules();
const opacityA = new Animated.Value(0);
@@ -1407,30 +1401,6 @@ describe('Native Animated', () => {
);
});
it('should restore default values on prop updates only', async () => {
const {ReactNativeFeatureFlags} = importModules();
ReactNativeFeatureFlags.override({enableAnimatedPropsMemo: () => false});
const {Animated} = importModules();
const opacity = new Animated.Value(0);
opacity.__makeNative();
const root = await create(<Animated.View style={{opacity}} />);
expect(NativeAnimatedModule.restoreDefaultValues).not.toHaveBeenCalled();
await update(root, <Animated.View style={{opacity}} />);
expect(NativeAnimatedModule.restoreDefaultValues).toHaveBeenCalledWith(
expect.any(Number),
);
await unmount(root);
// Make sure it doesn't get called on unmount.
expect(NativeAnimatedModule.restoreDefaultValues).toHaveBeenCalledTimes(
1,
);
});
it('connects the native view on mount and disconnects on unmount', async () => {
const {Animated} = importModules();
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<e1f16c89435be5abedd0a060ef036d95>>
* @generated SignedSource<<811a054f0ada56d8cc1ed21c6930fc52>>
* @flow strict
*/
@@ -32,16 +32,13 @@ export type ReactNativeFeatureFlagsJsOnly = $ReadOnly<{
animatedShouldUseSingleOp: Getter<boolean>,
disableInteractionManager: Getter<boolean>,
enableAccessToHostTreeInFabric: Getter<boolean>,
enableAnimatedAllowlist: Getter<boolean>,
enableAnimatedClearImmediateFix: Getter<boolean>,
enableAnimatedPropsMemo: Getter<boolean>,
fixVirtualizeListCollapseWindowSize: Getter<boolean>,
isLayoutAnimationEnabled: Getter<boolean>,
shouldSkipStateUpdatesForLoopingAnimations: Getter<boolean>,
shouldUseAnimatedObjectForTransform: Getter<boolean>,
shouldUseRemoveClippedSubviewsAsDefaultOnIOS: Getter<boolean>,
shouldUseSetNativePropsInFabric: Getter<boolean>,
useInsertionEffectsForAnimations: Getter<boolean>,
useRefsForTextInputState: Getter<boolean>,
}>;
@@ -123,21 +120,11 @@ export const disableInteractionManager: Getter<boolean> = createJavaScriptFlagGe
*/
export const enableAccessToHostTreeInFabric: Getter<boolean> = createJavaScriptFlagGetter('enableAccessToHostTreeInFabric', false);
/**
* Enables Animated to skip non-allowlisted props and styles.
*/
export const enableAnimatedAllowlist: Getter<boolean> = createJavaScriptFlagGetter('enableAnimatedAllowlist', true);
/**
* Enables an experimental to use the proper clearIntermediate instead of calling the wrong clearTimeout and canceling another timer.
*/
export const enableAnimatedClearImmediateFix: Getter<boolean> = createJavaScriptFlagGetter('enableAnimatedClearImmediateFix', true);
/**
* Enables Animated to analyze props to minimize invalidating `AnimatedProps`.
*/
export const enableAnimatedPropsMemo: Getter<boolean> = createJavaScriptFlagGetter('enableAnimatedPropsMemo', true);
/**
* Fixing an edge case where the current window size is not properly calculated with fast scrolling. Window size collapsed to 1 element even if windowSize more than the current amount of elements
*/
@@ -168,11 +155,6 @@ export const shouldUseRemoveClippedSubviewsAsDefaultOnIOS: Getter<boolean> = cre
*/
export const shouldUseSetNativePropsInFabric: Getter<boolean> = createJavaScriptFlagGetter('shouldUseSetNativePropsInFabric', true);
/**
* Changes construction of the animation graph to `useInsertionEffect` instead of `useLayoutEffect`.
*/
export const useInsertionEffectsForAnimations: Getter<boolean> = createJavaScriptFlagGetter('useInsertionEffectsForAnimations', true);
/**
* Enable a variant of TextInput that moves some state to refs to avoid unnecessary re-renders
*/