Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: reduce the amount of work done when rendering data #517

Merged
merged 1 commit into from
Dec 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/gold-onions-chew.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'react-native-reanimated-carousel': patch
---

Reduce the amount of work done when rendering data.
36 changes: 3 additions & 33 deletions src/components/BaseLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,10 @@ import React from "react";
import type { ViewStyle } from "react-native";
import type { AnimatedStyleProp } from "react-native-reanimated";
import Animated, {
runOnJS,
useAnimatedReaction,
useAnimatedStyle,
useDerivedValue,
} from "react-native-reanimated";

import { LazyView } from "./LazyView";

import { useCheckMounted } from "../hooks/useCheckMounted";
import type { IOpts } from "../hooks/useOffsetX";
import { useOffsetX } from "../hooks/useOffsetX";
import type { IVisibleRanges } from "../hooks/useVisibleRanges";
Expand All @@ -28,7 +23,6 @@ export const BaseLayout: React.FC<{
animationValue: Animated.SharedValue<number>
}) => React.ReactElement
}> = (props) => {
const mounted = useCheckMounted();
const { handlerOffset, index, children, visibleRanges, animationStyle }
= props;

Expand All @@ -46,7 +40,7 @@ export const BaseLayout: React.FC<{
},
} = context;
const size = vertical ? height : width;
const [shouldUpdate, setShouldUpdate] = React.useState(false);

let offsetXConfig: IOpts = {
handlerOffset,
index,
Expand Down Expand Up @@ -79,28 +73,6 @@ export const BaseLayout: React.FC<{
[animationStyle],
);

const updateView = React.useCallback(
(negativeRange: number[], positiveRange: number[]) => {
mounted.current
&& setShouldUpdate(
(index >= negativeRange[0] && index <= negativeRange[1])
|| (index >= positiveRange[0] && index <= positiveRange[1]),
);
},
[index, mounted],
);

useAnimatedReaction(
() => visibleRanges.value,
() => {
runOnJS(updateView)(
visibleRanges.value.negativeRange,
visibleRanges.value.positiveRange,
);
},
[visibleRanges.value],
);

return (
<Animated.View
style={[
Expand All @@ -116,11 +88,9 @@ export const BaseLayout: React.FC<{
* e.g.
* The testID of first item will be changed to __CAROUSEL_ITEM_0_READY__ from __CAROUSEL_ITEM_0_NOT_READY__ when the item is ready.
* */
testID={`__CAROUSEL_ITEM_${index}_${shouldUpdate ? "READY" : "NOT_READY"}__`}
testID={`__CAROUSEL_ITEM_${index}__`}
>
<LazyView shouldUpdate={shouldUpdate}>
{children({ animationValue })}
</LazyView>
{children({ animationValue })}
</Animated.View>
);
};
67 changes: 15 additions & 52 deletions src/components/Carousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { StyleSheet } from "react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { runOnJS, useDerivedValue } from "react-native-reanimated";

import { BaseLayout } from "./BaseLayout";
import { ItemRenderer } from "./ItemRenderer";
import { ScrollViewGesture } from "./ScrollViewGesture";

import { useAutoPlay } from "../hooks/useAutoPlay";
Expand All @@ -13,7 +13,6 @@ import { useInitProps } from "../hooks/useInitProps";
import { useLayoutConfig } from "../hooks/useLayoutConfig";
import { useOnProgressChange } from "../hooks/useOnProgressChange";
import { usePropsErrorBoundary } from "../hooks/usePropsErrorBoundary";
import { useVisibleRanges } from "../hooks/useVisibleRanges";
import { CTX } from "../store";
import type { ICarouselInstance, TCarouselProps } from "../types";
import { computedRealIndexWithAutoFillData } from "../utils/computed-with-auto-fill-data";
Expand All @@ -30,8 +29,6 @@ const Carousel = React.forwardRef<ICarouselInstance, TCarouselProps<any>>(
data,
// Length of fill data
dataLength,
// Raw data that has not been processed
rawData,
// Length of raw data
rawDataLength,
mode,
Expand Down Expand Up @@ -155,55 +152,8 @@ const Carousel = React.forwardRef<ICarouselInstance, TCarouselProps<any>>(
[getCurrentIndex, next, prev, scrollTo],
);

const visibleRanges = useVisibleRanges({
total: dataLength,
viewSize: size,
translation: handlerOffset,
windowSize,
loop,
});

const layoutConfig = useLayoutConfig({ ...props, size });

const renderLayout = React.useCallback(
(item: any, i: number) => {
const realIndex = computedRealIndexWithAutoFillData({
index: i,
dataLength: rawDataLength,
loop,
autoFillData,
});

return (
<BaseLayout
key={i}
index={i}
handlerOffset={offsetX}
visibleRanges={visibleRanges}
animationStyle={customAnimation || layoutConfig}
>
{({ animationValue }) =>
renderItem({
item,
index: realIndex,
animationValue,
})
}
</BaseLayout>
);
},
[
loop,
rawData,
offsetX,
visibleRanges,
autoFillData,
renderItem,
layoutConfig,
customAnimation,
],
);

return (
<GestureHandlerRootView>
<CTX.Provider value={{ props, common: commonVariables }}>
Expand All @@ -228,7 +178,20 @@ const Carousel = React.forwardRef<ICarouselInstance, TCarouselProps<any>>(
onTouchBegin={scrollViewGestureOnTouchBegin}
onTouchEnd={scrollViewGestureOnTouchEnd}
>
{data.map(renderLayout)}
<ItemRenderer
data={data}
dataLength={dataLength}
rawDataLength={rawDataLength}
loop={loop}
size={size}
windowSize={windowSize}
autoFillData={autoFillData}
offsetX={offsetX}
handlerOffset={handlerOffset}
layoutConfig={layoutConfig}
renderItem={renderItem}
customAnimation={customAnimation}
/>
</ScrollViewGesture>
</CTX.Provider>
</GestureHandlerRootView>
Expand Down
105 changes: 105 additions & 0 deletions src/components/ItemRenderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React from "react";
import type { FC } from "react";
import type { ViewStyle } from "react-native";
import type Animated from "react-native-reanimated";
import { useAnimatedReaction, type AnimatedStyleProp, runOnJS } from "react-native-reanimated";

import type { TAnimationStyle } from "./BaseLayout";
import { BaseLayout } from "./BaseLayout";

import type { VisibleRanges } from "../hooks/useVisibleRanges";
import { useVisibleRanges } from "../hooks/useVisibleRanges";
import type { CarouselRenderItem } from "../types";
import { computedRealIndexWithAutoFillData } from "../utils/computed-with-auto-fill-data";

interface Props {
data: any[]
dataLength: number
rawDataLength: number
loop: boolean
size: number
windowSize?: number
autoFillData: boolean
offsetX: Animated.SharedValue<number>
handlerOffset: Animated.SharedValue<number>
layoutConfig: TAnimationStyle
renderItem: CarouselRenderItem<any>
customAnimation?: ((value: number) => AnimatedStyleProp<ViewStyle>)
}

export const ItemRenderer: FC<Props> = (props) => {
const {
data,
size,
windowSize,
handlerOffset,
offsetX,
dataLength,
rawDataLength,
loop,
autoFillData,
layoutConfig,
renderItem,
customAnimation,
} = props;

const visibleRanges = useVisibleRanges({
total: dataLength,
viewSize: size,
translation: handlerOffset,
windowSize,
loop,
});

const [displayedItems, setDisplayedItems] = React.useState<VisibleRanges>(null!);

useAnimatedReaction(
() => visibleRanges.value,
ranges => runOnJS(setDisplayedItems)(ranges),
[visibleRanges],
);

if (!displayedItems)
return null;

return (
<>
{
data.map((item, index) => {
const realIndex = computedRealIndexWithAutoFillData({
index,
dataLength: rawDataLength,
loop,
autoFillData,
});

const { negativeRange, positiveRange } = displayedItems;

const shouldRender = (index >= negativeRange[0] && index <= negativeRange[1])
|| (index >= positiveRange[0] && index <= positiveRange[1]);

if (!shouldRender)
return null;

return (
<BaseLayout
key={index}
index={index}
handlerOffset={offsetX}
visibleRanges={visibleRanges}
animationStyle={customAnimation || layoutConfig}
>
{({ animationValue }) =>
renderItem({
item,
index: realIndex,
animationValue,
})
}
</BaseLayout>
);
})
}
</>
);
};
2 changes: 1 addition & 1 deletion src/hooks/useOffsetX.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ describe("useSharedValue", () => {
const range = useSharedValue({
negativeRange: [7, 9],
positiveRange: [0, 3],
});
}) as IVisibleRanges;
const inputs: Array<{
config: IOpts
range: IVisibleRanges
Expand Down
Loading