Skip to content

Commit 46a7892

Browse files
authored
Optimise ReanimatedSwipeable component (#3165)
## Description - Memoize all Gestures, and non-memoized functions. - Reduce number of used shared values - Fix unoptimal dependency lists on existing `useMemo`s ## Test plan - use `android`, `iOS` has no performance issues - use attached code to measure performance - tested on physical `Samsung S24 Ultra` - pre-optimization average: `19 fps` - post-optimization average: `68 fps` ## Test code Tested on `@shopify/flash-list` using their `useBenchmark` utility hook. <details> <summary> Collapsed code </summary> ```js import { Text, StyleSheet, View, Alert } from 'react-native'; import ReanimatedSwipeable from 'react-native-gesture-handler/ReanimatedSwipeable'; import { FlashList, ListRenderItem, useBenchmark } from '@shopify/flash-list'; import { memo, useRef } from 'react'; function RightAction() { return <Text style={styles.rightAction}>Text</Text>; } type Item = { id: string; title: string; }; const generateItems = (count: number): Item[] => { return Array.from({ length: count }, () => { const randomNumber = Math.random().toString(36).substring(2, 8); return { id: randomNumber, title: `Title ${randomNumber}`, }; }); }; const data = generateItems(200); const _RenderItemView = (item: Item) => ( <View style={styles.swipeable}> <Text>{item.title}</Text> </View> ); const RenderItemViewMemoed = memo(_RenderItemView); export default function Example() { const renderItem: ListRenderItem<Item> = ({ item }) => { return ( <ReanimatedSwipeable renderRightActions={RightAction}> <RenderItemViewMemoed {...item} /> </ReanimatedSwipeable> ); }; const flashListRef = useRef<FlashList<Item> | null>(null); useBenchmark(flashListRef, (callback) => { Alert.alert('result', callback.formattedString); }); return ( <FlashList ref={flashListRef} data={data} renderItem={renderItem} estimatedItemSize={50} keyExtractor={(item) => item.id} /> ); } const styles = StyleSheet.create({ leftAction: { width: 50, height: 50, backgroundColor: 'crimson' }, rightAction: { width: 50, height: 50, backgroundColor: 'purple' }, separator: { width: '100%', borderTopWidth: 1, }, swipeable: { height: 50, backgroundColor: 'papayawhip', alignItems: 'center', }, }); ``` </details>
1 parent ef686fc commit 46a7892

File tree

4 files changed

+665
-279
lines changed

4 files changed

+665
-279
lines changed

example/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"react-dom": "18.2.0",
3636
"react-native": "0.74.1",
3737
"react-native-gesture-handler": "link:..",
38-
"react-native-reanimated": "3.10.0",
38+
"react-native-reanimated": "3.15.5",
3939
"react-native-safe-area-context": "4.10.1",
4040
"react-native-screens": "3.31.1",
4141
"react-native-svg": "^15.8.0",

example/src/release_tests/swipeableReanimation/index.tsx

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
import React from 'react';
1+
import React, { useRef } from 'react';
22
import { Text, Animated, StyleSheet, View } from 'react-native';
33

44
import {
55
Swipeable,
66
GestureHandlerRootView,
7+
Pressable,
78
} from 'react-native-gesture-handler';
8-
import ReanimatedSwipeable from 'react-native-gesture-handler/ReanimatedSwipeable';
9+
import ReanimatedSwipeable, {
10+
SwipeableMethods,
11+
} from 'react-native-gesture-handler/ReanimatedSwipeable';
912
import Reanimated, {
1013
SharedValue,
1114
useAnimatedStyle,
@@ -92,11 +95,55 @@ function LegacyRightAction(prog: any, drag: any) {
9295
}
9396

9497
export default function Example() {
98+
const reanimatedRef = useRef<SwipeableMethods>(null);
99+
const legacyRef = useRef<Swipeable>(null);
100+
95101
return (
96102
<GestureHandlerRootView>
97103
<View style={styles.separator} />
98104

105+
<View style={styles.controlPanelWrapper}>
106+
<Text>Programatical controls</Text>
107+
<View style={styles.controlPanel}>
108+
<Pressable
109+
style={styles.control}
110+
onPress={() => {
111+
reanimatedRef.current!.openLeft();
112+
legacyRef.current?.openLeft();
113+
}}>
114+
<Text>open left</Text>
115+
</Pressable>
116+
<Pressable
117+
style={styles.control}
118+
onPress={() => {
119+
reanimatedRef.current!.close();
120+
legacyRef.current!.close();
121+
}}>
122+
<Text>close</Text>
123+
</Pressable>
124+
<Pressable
125+
style={styles.control}
126+
onPress={() => {
127+
reanimatedRef.current!.reset();
128+
legacyRef.current!.reset();
129+
}}>
130+
<Text>reset</Text>
131+
</Pressable>
132+
<Pressable
133+
style={styles.control}
134+
onPress={() => {
135+
reanimatedRef.current!.openRight();
136+
legacyRef.current!.openRight();
137+
}}>
138+
<Text>open right</Text>
139+
</Pressable>
140+
</View>
141+
</View>
142+
143+
<View style={styles.separator} />
144+
99145
<ReanimatedSwipeable
146+
ref={reanimatedRef}
100147
containerStyle={styles.swipeable}
101148
friction={2}
102149
leftThreshold={80}
@@ -110,6 +157,7 @@ export default function Example() {
110157
<View style={styles.separator} />
111158

112159
<Swipeable
160+
ref={legacyRef}
113161
containerStyle={styles.swipeable}
114162
friction={2}
115163
leftThreshold={80}
@@ -137,4 +185,20 @@ const styles = StyleSheet.create({
137185
backgroundColor: 'papayawhip',
138186
alignItems: 'center',
139187
},
188+
controlPanelWrapper: {
189+
backgroundColor: 'papayawhip',
190+
alignItems: 'center',
191+
},
192+
controlPanel: {
193+
backgroundColor: 'papayawhip',
194+
alignItems: 'center',
195+
flexDirection: 'row',
196+
},
197+
control: {
198+
flex: 1,
199+
height: 40,
200+
borderWidth: StyleSheet.hairlineWidth,
201+
alignItems: 'center',
202+
justifyContent: 'center',
203+
},
140204
});

0 commit comments

Comments
 (0)