Skip to content

Commit 9f3a3d6

Browse files
committed
perf: reduce the amount of work done when rendering data
Before, even if the limit of the number of render is set, it will render one more layer of BaseLayout, which makes the performance can not be maximized, and now the optimization makes BaseLayout will not render any more, even if the number of data is 1 million, it will only render the specified amount of render. Performance has improved dramatically. fix #352, fix #362, fix #258, fix #478
1 parent 766e267 commit 9f3a3d6

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)