Speed up animation on back button press + animated reattach on tab switch (#46438)

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

When hiding a view with React.Activity, React will "detach" all the views in teh subtree (call all their effect cleanups) and then upon showing it again will attach them (call all their effects again).

This was previously causing problems with useAnimatedProps and https://github.com/facebook/react-native/pull/46205 was intended to fix it (cc javache), but it's still a perf drain to have to detach all of this and then re-attach it upon show. Worse, it makes it so that we can't actually do any animations upon re-showing a view until all the passive effects have run to re-attach the views to the animations.

So instead, what this diff does is lean on the fact that useInsertionEffect *does not* clean up and rerun when Activity goes from hidden to visible and vice versa. Thus, we can rely on it to only detach the animated node when we're *actually* unmounting - rather than when we're simply hiding/showing.

Changelog:
[General][Changed] - Improved the performance of unmounting (and updating, when an enclosing Activity becomes hidden) Animated components

Reviewed By: yungsters, sammy-SC

Differential Revision: D62398039

fbshipit-source-id: af286e428188b5104f6cfd3fd4d8c9f791dedb8d
This commit is contained in:
Royi Hagigi
2024-09-11 15:00:01 -07:00
committed by Facebook GitHub Bot
parent 65d8f66b50
commit 46abda55b9
+26 -23
View File
@@ -22,6 +22,7 @@ import AnimatedValue from './nodes/AnimatedValue';
import {
useCallback,
useEffect,
useInsertionEffect,
useLayoutEffect,
useMemo,
useReducer,
@@ -314,8 +315,8 @@ function useAnimatedPropsLifecycle_layoutEffects(node: AnimatedProps): void {
* NOTE: unlike `useAnimatedPropsLifecycle_layoutEffects`, this version uses passive effects to setup animation graph.
*/
function useAnimatedPropsLifecycle_passiveEffects(node: AnimatedProps): void {
const prevNodeRef = useRef<?AnimatedProps>(null);
const isUnmountingRef = useRef<boolean>(false);
const attachedNodeRef =
useRef<?{node: AnimatedProps, listener: ?EventSubscription}>(null);
useEffect(() => {
// It is ok for multiple components to call `flushQueue` because it noops
@@ -324,43 +325,45 @@ function useAnimatedPropsLifecycle_passiveEffects(node: AnimatedProps): void {
NativeAnimatedHelper.API.flushQueue();
});
useEffect(() => {
isUnmountingRef.current = false;
useInsertionEffect(() => {
return () => {
isUnmountingRef.current = true;
const attachedNode = attachedNodeRef.current;
if (attachedNode != null) {
const {node: prevNode, listener} = attachedNode;
// NOTE: Do not restore default values on unmount, see D18197735.
prevNode.__detach();
listener?.remove();
attachedNodeRef.current = null;
}
};
}, []);
useEffect(() => {
node.__attach();
let drivenAnimationEndedListener: ?EventSubscription = null;
let prevAttachedNode = null;
if (attachedNodeRef.current !== node) {
node.__attach();
prevAttachedNode = attachedNodeRef.current;
let listener: ?EventSubscription = null;
if (node.__isNative) {
drivenAnimationEndedListener =
NativeAnimatedHelper.nativeEventEmitter.addListener(
if (node.__isNative) {
listener = NativeAnimatedHelper.nativeEventEmitter.addListener(
'onUserDrivenAnimationEnded',
data => {
node.update();
},
);
}
attachedNodeRef.current = {node, listener};
}
if (prevNodeRef.current != null) {
const prevNode = prevNodeRef.current;
if (prevAttachedNode != null) {
const {node: prevNode, listener} = prevAttachedNode;
// TODO: Stop restoring default values (unless `reset` is called).
prevNode.__restoreDefaultValues();
prevNode.__detach();
prevNodeRef.current = null;
listener?.remove();
}
return () => {
if (isUnmountingRef.current) {
// NOTE: Do not restore default values on unmount, see D18197735.
node.__detach();
} else {
prevNodeRef.current = node;
}
drivenAnimationEndedListener?.remove();
};
}, [node]);
}