From 46abda55b9749dc171e0fec551ba83c027818eb7 Mon Sep 17 00:00:00 2001 From: Royi Hagigi Date: Wed, 11 Sep 2024 15:00:01 -0700 Subject: [PATCH] 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 --- .../Libraries/Animated/useAnimatedProps.js | 49 ++++++++++--------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/packages/react-native/Libraries/Animated/useAnimatedProps.js b/packages/react-native/Libraries/Animated/useAnimatedProps.js index 28086bedfa6..0b79eb12666 100644 --- a/packages/react-native/Libraries/Animated/useAnimatedProps.js +++ b/packages/react-native/Libraries/Animated/useAnimatedProps.js @@ -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(null); - const isUnmountingRef = useRef(false); + const attachedNodeRef = + useRef(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]); }