From dbee30c4f15d90e39143bbbaa8ad46fd61dacc54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacy=20=C5=81=C4=85tka?= Date: Thu, 17 Oct 2024 13:25:19 +0200 Subject: [PATCH 1/4] memoize gestures, functions, cleanup --- src/components/ReanimatedSwipeable.tsx | 230 +++++++++++++++---------- 1 file changed, 135 insertions(+), 95 deletions(-) diff --git a/src/components/ReanimatedSwipeable.tsx b/src/components/ReanimatedSwipeable.tsx index 8bcc56921..982ac5c25 100644 --- a/src/components/ReanimatedSwipeable.tsx +++ b/src/components/ReanimatedSwipeable.tsx @@ -7,6 +7,7 @@ import React, { forwardRef, useCallback, useImperativeHandle, + useMemo, useRef, } from 'react'; import { GestureObjects as Gesture } from '../handlers/gestures/gestureObjects'; @@ -217,8 +218,6 @@ const Swipeable = forwardRef( const { leftThreshold, rightThreshold, - onSwipeableOpenStartDrag, - onSwipeableCloseStartDrag, enableTrackpadTwoFingerGesture, enabled, containerStyle, @@ -226,11 +225,18 @@ const Swipeable = forwardRef( animationOptions, overshootLeft, overshootRight, + testID, + children, + dragOffsetFromLeftEdge = 10, + dragOffsetFromRightEdge = 10, + onSwipeableOpenStartDrag, + onSwipeableCloseStartDrag, onSwipeableWillOpen, onSwipeableWillClose, onSwipeableOpen, onSwipeableClose, - testID, + renderLeftActions, + renderRightActions, ...remainingProps } = props; @@ -275,15 +281,15 @@ const Swipeable = forwardRef( const overshootLeftProp = overshootLeft; const overshootRightProp = overshootRight; - const updateRightElementWidth = () => { + const updateRightElementWidth = useCallback(() => { 'worklet'; if (rightOffset.value === null) { rightOffset.value = rowWidth.value; } rightWidth.value = Math.max(0, rowWidth.value - rightOffset.value); - }; + }, [rightOffset, rightWidth, rowWidth.value]); - const updateAnimatedEvent = () => { + const updateAnimatedEvent = useCallback(() => { 'worklet'; updateRightElementWidth(); @@ -333,7 +339,20 @@ const Swipeable = forwardRef( [1, 0, 0] ) : 0; - }; + }, [ + appliedTranslation, + friction, + leftWidth.value, + overshootFriction, + overshootLeftProp, + overshootRightProp, + rightWidth.value, + rowState.value, + showLeftProgress, + showRightProgress, + updateRightElementWidth, + userDrag.value, + ]); const dispatchImmediateEvents = useCallback( (fromValue: number, toValue: number) => { @@ -445,14 +464,6 @@ const Swipeable = forwardRef( rowWidth.value = nativeEvent.layout.width; }; - const { - children, - renderLeftActions, - renderRightActions, - dragOffsetFromLeftEdge = 10, - dragOffsetFromRightEdge = 10, - } = props; - swipeableMethods.current = { close() { 'worklet'; @@ -508,105 +519,134 @@ const Swipeable = forwardRef( const leftThresholdProp = leftThreshold; const rightThresholdProp = rightThreshold; - const handleRelease = ( - event: GestureStateChangeEvent - ) => { - 'worklet'; - const { velocityX } = event; - userDrag.value = event.translationX; + const handleRelease = useCallback( + (event: GestureStateChangeEvent) => { + 'worklet'; + const { velocityX } = event; + userDrag.value = event.translationX; - updateRightElementWidth(); + updateRightElementWidth(); - const leftThreshold = leftThresholdProp ?? leftWidth.value / 2; - const rightThreshold = rightThresholdProp ?? rightWidth.value / 2; + const leftThreshold = leftThresholdProp ?? leftWidth.value / 2; + const rightThreshold = rightThresholdProp ?? rightWidth.value / 2; - const translationX = (userDrag.value + DRAG_TOSS * velocityX) / friction; + const translationX = + (userDrag.value + DRAG_TOSS * velocityX) / friction; - let toValue = 0; + let toValue = 0; - if (rowState.value === 0) { - if (translationX > leftThreshold) { - toValue = leftWidth.value; - } else if (translationX < -rightThreshold) { - toValue = -rightWidth.value; - } - } else if (rowState.value === 1) { - // Swiped to left - if (translationX > -leftThreshold) { - toValue = leftWidth.value; - } - } else { - // Swiped to right - if (translationX < rightThreshold) { - toValue = -rightWidth.value; + if (rowState.value === 0) { + if (translationX > leftThreshold) { + toValue = leftWidth.value; + } else if (translationX < -rightThreshold) { + toValue = -rightWidth.value; + } + } else if (rowState.value === 1) { + // Swiped to left + if (translationX > -leftThreshold) { + toValue = leftWidth.value; + } + } else { + // Swiped to right + if (translationX < rightThreshold) { + toValue = -rightWidth.value; + } } - } - animateRow(toValue, velocityX / friction); - }; + animateRow(toValue, velocityX / friction); + }, + [ + animateRow, + friction, + leftThresholdProp, + leftWidth.value, + rightThresholdProp, + rightWidth.value, + rowState.value, + updateRightElementWidth, + userDrag, + ] + ); - const close = () => { + const close = useCallback(() => { 'worklet'; animateRow(0); - }; - - const tapGesture = Gesture.Tap().onStart(() => { - if (rowState.value !== 0) { - close(); - } - }); + }, [animateRow]); const dragStarted = useSharedValue(false); - const panGesture = Gesture.Pan() - .onUpdate((event: GestureUpdateEvent) => { - userDrag.value = event.translationX; - - const direction = - rowState.value === -1 - ? SwipeDirection.RIGHT - : rowState.value === 1 - ? SwipeDirection.LEFT - : event.translationX > 0 - ? SwipeDirection.RIGHT - : SwipeDirection.LEFT; - - if (!dragStarted.value) { - dragStarted.value = true; - if (rowState.value === 0 && onSwipeableOpenStartDrag) { - runOnJS(onSwipeableOpenStartDrag)(direction); - } else if (rowState.value !== 0 && onSwipeableCloseStartDrag) { - runOnJS(onSwipeableCloseStartDrag)(direction); - } - } + const tapGesture = useMemo( + () => + Gesture.Tap() + .shouldCancelWhenOutside(true) + .onStart(() => { + if (rowState.value !== 0) { + close(); + } + }), + [close, rowState.value] + ); - updateAnimatedEvent(); - }) - .onEnd( - (event: GestureStateChangeEvent) => { - handleRelease(event); - } - ) - .onFinalize(() => { - dragStarted.value = false; - }); - - if (enableTrackpadTwoFingerGesture) { - panGesture.enableTrackpadTwoFingerGesture(enableTrackpadTwoFingerGesture); - } - - panGesture.activeOffsetX([ - -dragOffsetFromRightEdge, - dragOffsetFromLeftEdge, - ]); - tapGesture.shouldCancelWhenOutside(true); + const panGesture = useMemo( + () => + Gesture.Pan() + .enabled(enabled !== false) + .enableTrackpadTwoFingerGesture( + enableTrackpadTwoFingerGesture ?? false + ) + .activeOffsetX([-dragOffsetFromRightEdge, dragOffsetFromLeftEdge]) + .onUpdate( + (event: GestureUpdateEvent) => { + userDrag.value = event.translationX; + + const direction = + rowState.value === -1 + ? SwipeDirection.RIGHT + : rowState.value === 1 + ? SwipeDirection.LEFT + : event.translationX > 0 + ? SwipeDirection.RIGHT + : SwipeDirection.LEFT; + + if (!dragStarted.value) { + dragStarted.value = true; + if (rowState.value === 0 && onSwipeableOpenStartDrag) { + runOnJS(onSwipeableOpenStartDrag)(direction); + } else if (rowState.value !== 0 && onSwipeableCloseStartDrag) { + runOnJS(onSwipeableCloseStartDrag)(direction); + } + } + + updateAnimatedEvent(); + } + ) + .onEnd( + (event: GestureStateChangeEvent) => { + handleRelease(event); + } + ) + .onFinalize(() => { + dragStarted.value = false; + }), + [ + dragOffsetFromLeftEdge, + dragOffsetFromRightEdge, + dragStarted, + enableTrackpadTwoFingerGesture, + enabled, + handleRelease, + onSwipeableCloseStartDrag, + onSwipeableOpenStartDrag, + rowState.value, + updateAnimatedEvent, + userDrag, + ] + ); useImperativeHandle(ref, () => swipeableMethods.current, [ swipeableMethods, ]); - panGesture.enabled(enabled !== false); - const animatedStyle = useAnimatedStyle( () => ({ transform: [{ translateX: appliedTranslation.value }], From fb8302a16d667fa7b35b1fcafcd78451f5f5518e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacy=20=C5=81=C4=85tka?= Date: Thu, 17 Oct 2024 15:19:25 +0200 Subject: [PATCH 2/4] memoize more elements, offload more logic into UI --- src/components/ReanimatedSwipeable.tsx | 98 ++++++++++++++++---------- 1 file changed, 59 insertions(+), 39 deletions(-) diff --git a/src/components/ReanimatedSwipeable.tsx b/src/components/ReanimatedSwipeable.tsx index 982ac5c25..721de12d7 100644 --- a/src/components/ReanimatedSwipeable.tsx +++ b/src/components/ReanimatedSwipeable.tsx @@ -356,19 +356,26 @@ const Swipeable = forwardRef( const dispatchImmediateEvents = useCallback( (fromValue: number, toValue: number) => { + 'worklet'; if (toValue > 0) { - onSwipeableWillOpen?.(SwipeDirection.RIGHT); + onSwipeableWillOpen && + runOnJS(onSwipeableWillOpen)(SwipeDirection.RIGHT); } else if (toValue < 0) { - onSwipeableWillOpen?.(SwipeDirection.LEFT); + onSwipeableWillOpen && + runOnJS(onSwipeableWillOpen)(SwipeDirection.LEFT); } else { - onSwipeableWillClose?.( - fromValue > 0 ? SwipeDirection.LEFT : SwipeDirection.RIGHT - ); + onSwipeableWillClose && + runOnJS(onSwipeableWillClose)( + fromValue > 0 ? SwipeDirection.LEFT : SwipeDirection.RIGHT + ); } }, [onSwipeableWillClose, onSwipeableWillOpen] ); + // Cannot be converted to 'worklet' due to: + // Error: [Reanimated] Trying to convert a cyclic object to a shareable. This is not supported. + // The other event dispatcher works fine. const dispatchEndEvents = useCallback( (fromValue: number, toValue: number) => { if (toValue > 0) { @@ -443,7 +450,7 @@ const Swipeable = forwardRef( ? withSpring(progressTarget, progressSpringConfig) : 0; - runOnJS(dispatchImmediateEvents)(frozenRowState, toValue); + dispatchImmediateEvents(frozenRowState, toValue); rowState.value = Math.sign(toValue); }, @@ -460,9 +467,12 @@ const Swipeable = forwardRef( ] ); - const onRowLayout = ({ nativeEvent }: LayoutChangeEvent) => { - rowWidth.value = nativeEvent.layout.width; - }; + const onRowLayout = useCallback( + ({ nativeEvent }: LayoutChangeEvent) => { + rowWidth.value = nativeEvent.layout.width; + }, + [rowWidth] + ); swipeableMethods.current = { close() { @@ -486,34 +496,42 @@ const Swipeable = forwardRef( }, }; - const leftElement = renderLeftActions && ( - - {renderLeftActions( - showLeftProgress, - appliedTranslation, - swipeableMethods.current - )} - - (leftWidth.value = nativeEvent.layout.x) - } - /> - + const leftElement = useMemo( + () => + renderLeftActions && ( + + {renderLeftActions( + showLeftProgress, + appliedTranslation, + swipeableMethods.current + )} + + (leftWidth.value = nativeEvent.layout.x) + } + /> + + ), + [appliedTranslation, leftWidth, renderLeftActions, showLeftProgress] ); - const rightElement = renderRightActions && ( - - {renderRightActions( - showRightProgress, - appliedTranslation, - swipeableMethods.current - )} - - (rightOffset.value = nativeEvent.layout.x) - } - /> - + const rightElement = useMemo( + () => + renderRightActions && ( + + {renderRightActions( + showRightProgress, + appliedTranslation, + swipeableMethods.current + )} + + (rightOffset.value = nativeEvent.layout.x) + } + /> + + ), + [appliedTranslation, renderRightActions, rightOffset, showRightProgress] ); const leftThresholdProp = leftThreshold; @@ -610,10 +628,12 @@ const Swipeable = forwardRef( if (!dragStarted.value) { dragStarted.value = true; - if (rowState.value === 0 && onSwipeableOpenStartDrag) { - runOnJS(onSwipeableOpenStartDrag)(direction); - } else if (rowState.value !== 0 && onSwipeableCloseStartDrag) { - runOnJS(onSwipeableCloseStartDrag)(direction); + if (rowState.value === 0) { + onSwipeableOpenStartDrag && + runOnJS(onSwipeableOpenStartDrag)(direction); + } else if (rowState.value !== 0) { + onSwipeableCloseStartDrag && + runOnJS(onSwipeableCloseStartDrag)(direction); } } From fc398dd7b3e5decd38ef25d078fdb5b44a3109e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacy=20=C5=81=C4=85tka?= Date: Thu, 17 Oct 2024 15:55:54 +0200 Subject: [PATCH 3/4] simplify prop extraction, add simplification comments --- src/components/ReanimatedSwipeable.tsx | 48 +++++++++++++------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/components/ReanimatedSwipeable.tsx b/src/components/ReanimatedSwipeable.tsx index 721de12d7..80e9185e3 100644 --- a/src/components/ReanimatedSwipeable.tsx +++ b/src/components/ReanimatedSwipeable.tsx @@ -215,6 +215,12 @@ const Swipeable = forwardRef( props: SwipeableProps, ref: ForwardedRef ) { + const defaultProps = { + friction: 1, + overshootFriction: 1, + dragOffset: 10, + }; + const { leftThreshold, rightThreshold, @@ -227,8 +233,10 @@ const Swipeable = forwardRef( overshootRight, testID, children, - dragOffsetFromLeftEdge = 10, - dragOffsetFromRightEdge = 10, + dragOffsetFromLeftEdge = defaultProps.dragOffset, + dragOffsetFromRightEdge = defaultProps.dragOffset, + friction = defaultProps.friction, + overshootFriction = defaultProps.overshootFriction, onSwipeableOpenStartDrag, onSwipeableCloseStartDrag, onSwipeableWillOpen, @@ -242,14 +250,19 @@ const Swipeable = forwardRef( const rowState = useSharedValue(0); + // %% cache value, see if they are already provided in the event const userDrag = useSharedValue(0); + + // %% same-ish const appliedTranslation = useSharedValue(0); + // %% these likely could be removed, see if rightOffset is neccessary const rowWidth = useSharedValue(0); const leftWidth = useSharedValue(0); const rightWidth = useSharedValue(0); const rightOffset = useSharedValue(null); + // %% progression cache, houses withSpring output const showLeftProgress = useSharedValue(0); const showRightProgress = useSharedValue(0); @@ -268,16 +281,6 @@ const Swipeable = forwardRef( }, }); - const defaultProps = { - friction: 1, - overshootFriction: 1, - }; - - const { - friction = defaultProps.friction, - overshootFriction = defaultProps.overshootFriction, - } = props; - const overshootLeftProp = overshootLeft; const overshootRightProp = overshootRight; @@ -373,7 +376,7 @@ const Swipeable = forwardRef( [onSwipeableWillClose, onSwipeableWillOpen] ); - // Cannot be converted to 'worklet' due to: + // Fixme: Cannot be converted to 'worklet' due to: // Error: [Reanimated] Trying to convert a cyclic object to a shareable. This is not supported. // The other event dispatcher works fine. const dispatchEndEvents = useCallback( @@ -534,9 +537,6 @@ const Swipeable = forwardRef( [appliedTranslation, renderRightActions, rightOffset, showRightProgress] ); - const leftThresholdProp = leftThreshold; - const rightThresholdProp = rightThreshold; - const handleRelease = useCallback( (event: GestureStateChangeEvent) => { 'worklet'; @@ -545,8 +545,8 @@ const Swipeable = forwardRef( updateRightElementWidth(); - const leftThreshold = leftThresholdProp ?? leftWidth.value / 2; - const rightThreshold = rightThresholdProp ?? rightWidth.value / 2; + const leftThresholdProp = leftThreshold ?? leftWidth.value / 2; + const rightThresholdProp = rightThreshold ?? rightWidth.value / 2; const translationX = (userDrag.value + DRAG_TOSS * velocityX) / friction; @@ -554,19 +554,19 @@ const Swipeable = forwardRef( let toValue = 0; if (rowState.value === 0) { - if (translationX > leftThreshold) { + if (translationX > leftThresholdProp) { toValue = leftWidth.value; - } else if (translationX < -rightThreshold) { + } else if (translationX < -rightThresholdProp) { toValue = -rightWidth.value; } } else if (rowState.value === 1) { // Swiped to left - if (translationX > -leftThreshold) { + if (translationX > -leftThresholdProp) { toValue = leftWidth.value; } } else { // Swiped to right - if (translationX < rightThreshold) { + if (translationX < rightThresholdProp) { toValue = -rightWidth.value; } } @@ -576,9 +576,9 @@ const Swipeable = forwardRef( [ animateRow, friction, - leftThresholdProp, + leftThreshold, leftWidth.value, - rightThresholdProp, + rightThreshold, rightWidth.value, rowState.value, updateRightElementWidth, From 104761f5971aaa9af097f889e0245b8e25202d04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacy=20=C5=81=C4=85tka?= Date: Fri, 18 Oct 2024 13:11:48 +0200 Subject: [PATCH 4/4] memo method setting, memo the final returned component --- src/components/ReanimatedSwipeable.tsx | 97 ++++++++++++++++---------- 1 file changed, 61 insertions(+), 36 deletions(-) diff --git a/src/components/ReanimatedSwipeable.tsx b/src/components/ReanimatedSwipeable.tsx index 80e9185e3..03a976a1b 100644 --- a/src/components/ReanimatedSwipeable.tsx +++ b/src/components/ReanimatedSwipeable.tsx @@ -6,6 +6,7 @@ import React, { ForwardedRef, forwardRef, useCallback, + useEffect, useImperativeHandle, useMemo, useRef, @@ -477,27 +478,37 @@ const Swipeable = forwardRef( [rowWidth] ); - swipeableMethods.current = { - close() { - 'worklet'; - animateRow(0); - }, - openLeft() { - 'worklet'; - animateRow(leftWidth.value); - }, - openRight() { - 'worklet'; - animateRow(-rightWidth.value); - }, - reset() { - 'worklet'; - userDrag.value = 0; - showLeftProgress.value = 0; - appliedTranslation.value = 0; - rowState.value = 0; - }, - }; + useEffect(() => { + swipeableMethods.current = { + close() { + 'worklet'; + animateRow(0); + }, + openLeft() { + 'worklet'; + animateRow(leftWidth.value); + }, + openRight() { + 'worklet'; + animateRow(-rightWidth.value); + }, + reset() { + 'worklet'; + userDrag.value = 0; + showLeftProgress.value = 0; + appliedTranslation.value = 0; + rowState.value = 0; + }, + }; + }, [ + animateRow, + appliedTranslation, + leftWidth.value, + rightWidth.value, + rowState, + showLeftProgress, + userDrag, + ]); const leftElement = useMemo( () => @@ -675,21 +686,35 @@ const Swipeable = forwardRef( [appliedTranslation, rowState] ); - const swipeableComponent = ( - - - {leftElement} - {rightElement} - - - {children} - - - - + const swipeableComponent = useMemo( + () => ( + + + {leftElement} + {rightElement} + + + {children} + + + + + ), + [ + animatedStyle, + children, + childrenContainerStyle, + containerStyle, + leftElement, + onRowLayout, + panGesture, + remainingProps, + rightElement, + tapGesture, + ] ); return testID ? (