Skip to content

Commit 6a57c83

Browse files
authored
Merge pull request #517 from dohooo/develop
perf: reduce the amount of work done when rendering data
2 parents 25a879c + 9f3a3d6 commit 6a57c83

File tree

6 files changed

+183
-108
lines changed

6 files changed

+183
-108
lines changed

.changeset/gold-onions-chew.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'react-native-reanimated-carousel': patch
3+
---
4+
5+
Reduce the amount of work done when rendering data.

src/components/BaseLayout.tsx

Lines changed: 3 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,10 @@ import React from "react";
22
import type { ViewStyle } from "react-native";
33
import type { AnimatedStyleProp } from "react-native-reanimated";
44
import Animated, {
5-
runOnJS,
6-
useAnimatedReaction,
75
useAnimatedStyle,
86
useDerivedValue,
97
} from "react-native-reanimated";
108

11-
import { LazyView } from "./LazyView";
12-
13-
import { useCheckMounted } from "../hooks/useCheckMounted";
149
import type { IOpts } from "../hooks/useOffsetX";
1510
import { useOffsetX } from "../hooks/useOffsetX";
1611
import type { IVisibleRanges } from "../hooks/useVisibleRanges";
@@ -28,7 +23,6 @@ export const BaseLayout: React.FC<{
2823
animationValue: Animated.SharedValue<number>
2924
}) => React.ReactElement
3025
}> = (props) => {
31-
const mounted = useCheckMounted();
3226
const { handlerOffset, index, children, visibleRanges, animationStyle }
3327
= props;
3428

@@ -46,7 +40,7 @@ export const BaseLayout: React.FC<{
4640
},
4741
} = context;
4842
const size = vertical ? height : width;
49-
const [shouldUpdate, setShouldUpdate] = React.useState(false);
43+
5044
let offsetXConfig: IOpts = {
5145
handlerOffset,
5246
index,
@@ -79,28 +73,6 @@ export const BaseLayout: React.FC<{
7973
[animationStyle],
8074
);
8175

82-
const updateView = React.useCallback(
83-
(negativeRange: number[], positiveRange: number[]) => {
84-
mounted.current
85-
&& setShouldUpdate(
86-
(index >= negativeRange[0] && index <= negativeRange[1])
87-
|| (index >= positiveRange[0] && index <= positiveRange[1]),
88-
);
89-
},
90-
[index, mounted],
91-
);
92-
93-
useAnimatedReaction(
94-
() => visibleRanges.value,
95-
() => {
96-
runOnJS(updateView)(
97-
visibleRanges.value.negativeRange,
98-
visibleRanges.value.positiveRange,
99-
);
100-
},
101-
[visibleRanges.value],
102-
);
103-
10476
return (
10577
<Animated.View
10678
style={[
@@ -116,11 +88,9 @@ export const BaseLayout: React.FC<{
11688
* e.g.
11789
* The testID of first item will be changed to __CAROUSEL_ITEM_0_READY__ from __CAROUSEL_ITEM_0_NOT_READY__ when the item is ready.
11890
* */
119-
testID={`__CAROUSEL_ITEM_${index}_${shouldUpdate ? "READY" : "NOT_READY"}__`}
91+
testID={`__CAROUSEL_ITEM_${index}__`}
12092
>
121-
<LazyView shouldUpdate={shouldUpdate}>
122-
{children({ animationValue })}
123-
</LazyView>
93+
{children({ animationValue })}
12494
</Animated.View>
12595
);
12696
};

src/components/Carousel.tsx

Lines changed: 15 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { StyleSheet } from "react-native";
33
import { GestureHandlerRootView } from "react-native-gesture-handler";
44
import { runOnJS, useDerivedValue } from "react-native-reanimated";
55

6-
import { BaseLayout } from "./BaseLayout";
6+
import { ItemRenderer } from "./ItemRenderer";
77
import { ScrollViewGesture } from "./ScrollViewGesture";
88

99
import { useAutoPlay } from "../hooks/useAutoPlay";
@@ -13,7 +13,6 @@ import { useInitProps } from "../hooks/useInitProps";
1313
import { useLayoutConfig } from "../hooks/useLayoutConfig";
1414
import { useOnProgressChange } from "../hooks/useOnProgressChange";
1515
import { usePropsErrorBoundary } from "../hooks/usePropsErrorBoundary";
16-
import { useVisibleRanges } from "../hooks/useVisibleRanges";
1716
import { CTX } from "../store";
1817
import type { ICarouselInstance, TCarouselProps } from "../types";
1918
import { computedRealIndexWithAutoFillData } from "../utils/computed-with-auto-fill-data";
@@ -30,8 +29,6 @@ const Carousel = React.forwardRef<ICarouselInstance, TCarouselProps<any>>(
3029
data,
3130
// Length of fill data
3231
dataLength,
33-
// Raw data that has not been processed
34-
rawData,
3532
// Length of raw data
3633
rawDataLength,
3734
mode,
@@ -155,55 +152,8 @@ const Carousel = React.forwardRef<ICarouselInstance, TCarouselProps<any>>(
155152
[getCurrentIndex, next, prev, scrollTo],
156153
);
157154

158-
const visibleRanges = useVisibleRanges({
159-
total: dataLength,
160-
viewSize: size,
161-
translation: handlerOffset,
162-
windowSize,
163-
loop,
164-
});
165-
166155
const layoutConfig = useLayoutConfig({ ...props, size });
167156

168-
const renderLayout = React.useCallback(
169-
(item: any, i: number) => {
170-
const realIndex = computedRealIndexWithAutoFillData({
171-
index: i,
172-
dataLength: rawDataLength,
173-
loop,
174-
autoFillData,
175-
});
176-
177-
return (
178-
<BaseLayout
179-
key={i}
180-
index={i}
181-
handlerOffset={offsetX}
182-
visibleRanges={visibleRanges}
183-
animationStyle={customAnimation || layoutConfig}
184-
>
185-
{({ animationValue }) =>
186-
renderItem({
187-
item,
188-
index: realIndex,
189-
animationValue,
190-
})
191-
}
192-
</BaseLayout>
193-
);
194-
},
195-
[
196-
loop,
197-
rawData,
198-
offsetX,
199-
visibleRanges,
200-
autoFillData,
201-
renderItem,
202-
layoutConfig,
203-
customAnimation,
204-
],
205-
);
206-
207157
return (
208158
<GestureHandlerRootView>
209159
<CTX.Provider value={{ props, common: commonVariables }}>
@@ -228,7 +178,20 @@ const Carousel = React.forwardRef<ICarouselInstance, TCarouselProps<any>>(
228178
onTouchBegin={scrollViewGestureOnTouchBegin}
229179
onTouchEnd={scrollViewGestureOnTouchEnd}
230180
>
231-
{data.map(renderLayout)}
181+
<ItemRenderer
182+
data={data}
183+
dataLength={dataLength}
184+
rawDataLength={rawDataLength}
185+
loop={loop}
186+
size={size}
187+
windowSize={windowSize}
188+
autoFillData={autoFillData}
189+
offsetX={offsetX}
190+
handlerOffset={handlerOffset}
191+
layoutConfig={layoutConfig}
192+
renderItem={renderItem}
193+
customAnimation={customAnimation}
194+
/>
232195
</ScrollViewGesture>
233196
</CTX.Provider>
234197
</GestureHandlerRootView>

src/components/ItemRenderer.tsx

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import React from "react";
2+
import type { FC } from "react";
3+
import type { ViewStyle } from "react-native";
4+
import type Animated from "react-native-reanimated";
5+
import { useAnimatedReaction, type AnimatedStyleProp, runOnJS } from "react-native-reanimated";
6+
7+
import type { TAnimationStyle } from "./BaseLayout";
8+
import { BaseLayout } from "./BaseLayout";
9+
10+
import type { VisibleRanges } from "../hooks/useVisibleRanges";
11+
import { useVisibleRanges } from "../hooks/useVisibleRanges";
12+
import type { CarouselRenderItem } from "../types";
13+
import { computedRealIndexWithAutoFillData } from "../utils/computed-with-auto-fill-data";
14+
15+
interface Props {
16+
data: any[]
17+
dataLength: number
18+
rawDataLength: number
19+
loop: boolean
20+
size: number
21+
windowSize?: number
22+
autoFillData: boolean
23+
offsetX: Animated.SharedValue<number>
24+
handlerOffset: Animated.SharedValue<number>
25+
layoutConfig: TAnimationStyle
26+
renderItem: CarouselRenderItem<any>
27+
customAnimation?: ((value: number) => AnimatedStyleProp<ViewStyle>)
28+
}
29+
30+
export const ItemRenderer: FC<Props> = (props) => {
31+
const {
32+
data,
33+
size,
34+
windowSize,
35+
handlerOffset,
36+
offsetX,
37+
dataLength,
38+
rawDataLength,
39+
loop,
40+
autoFillData,
41+
layoutConfig,
42+
renderItem,
43+
customAnimation,
44+
} = props;
45+
46+
const visibleRanges = useVisibleRanges({
47+
total: dataLength,
48+
viewSize: size,
49+
translation: handlerOffset,
50+
windowSize,
51+
loop,
52+
});
53+
54+
const [displayedItems, setDisplayedItems] = React.useState<VisibleRanges>(null!);
55+
56+
useAnimatedReaction(
57+
() => visibleRanges.value,
58+
ranges => runOnJS(setDisplayedItems)(ranges),
59+
[visibleRanges],
60+
);
61+
62+
if (!displayedItems)
63+
return null;
64+
65+
return (
66+
<>
67+
{
68+
data.map((item, index) => {
69+
const realIndex = computedRealIndexWithAutoFillData({
70+
index,
71+
dataLength: rawDataLength,
72+
loop,
73+
autoFillData,
74+
});
75+
76+
const { negativeRange, positiveRange } = displayedItems;
77+
78+
const shouldRender = (index >= negativeRange[0] && index <= negativeRange[1])
79+
|| (index >= positiveRange[0] && index <= positiveRange[1]);
80+
81+
if (!shouldRender)
82+
return null;
83+
84+
return (
85+
<BaseLayout
86+
key={index}
87+
index={index}
88+
handlerOffset={offsetX}
89+
visibleRanges={visibleRanges}
90+
animationStyle={customAnimation || layoutConfig}
91+
>
92+
{({ animationValue }) =>
93+
renderItem({
94+
item,
95+
index: realIndex,
96+
animationValue,
97+
})
98+
}
99+
</BaseLayout>
100+
);
101+
})
102+
}
103+
</>
104+
);
105+
};

src/hooks/useOffsetX.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ describe("useSharedValue", () => {
1212
const range = useSharedValue({
1313
negativeRange: [7, 9],
1414
positiveRange: [0, 3],
15-
});
15+
}) as IVisibleRanges;
1616
const inputs: Array<{
1717
config: IOpts
1818
range: IVisibleRanges

0 commit comments

Comments
 (0)