perf(rn): memo and optimize list

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei 2025-01-13 15:33:55 +08:00
parent 6abb442f03
commit 89126ed83e
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
3 changed files with 83 additions and 31 deletions

View File

@ -1,9 +1,18 @@
import { cn } from "@follow/utils"
import { debounce } from "es-toolkit/compat"
import type { FC } from "react"
import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react"
import {
forwardRef,
memo,
useCallback,
useEffect,
useImperativeHandle,
useRef,
useState,
} from "react"
import type {
Animated as AnimatedNative,
LayoutChangeEvent,
StyleProp,
TouchableOpacityProps,
ViewStyle,
@ -115,7 +124,19 @@ export const TabBar = forwardRef<ScrollView, TabBarProps>(
}
}
}, [currentTab, indicatorPosition, sharedPagerOffsetX.value, tabPositions, tabWidths])
const handleTabItemLayout = useCallback((event: LayoutChangeEvent, index: number) => {
const { width, x } = event.nativeEvent.layout
setTabWidths((prev) => {
const newWidths = [...prev]
newWidths[index] = width
return newWidths
})
setTabPositions((prev) => {
const newPositions = [...prev]
newPositions[index] = x
return newPositions
})
}, [])
const indicatorStyle = useAnimatedStyle(() => {
const scrollProgress = Math.max(sharedPagerOffsetX.value / tabBarWidth, 0)
@ -155,29 +176,15 @@ export const TabBar = forwardRef<ScrollView, TabBarProps>(
style={[styles.root, tabbarStyle]}
>
{tabs.map((tab, index) => (
<TabItem
onPress={() => {
handleChangeTabIndex(index)
}}
<TarBarItem
TabItem={TabItem}
key={tab.value}
isSelected={index === currentTab}
onLayout={(event) => {
const { width, x } = event.nativeEvent.layout
setTabWidths((prev) => {
const newWidths = [...prev]
newWidths[index] = width
return newWidths
})
setTabPositions((prev) => {
const newPositions = [...prev]
newPositions[index] = x
return newPositions
})
}}
index={index}
onTabItemPress={handleChangeTabIndex}
onLayout={handleTabItemLayout}
isSelected={currentTab === index}
tab={tab}
>
<TabItemInner tab={tab} isSelected={index === currentTab} />
</TabItem>
/>
))}
<Animated.View style={[styles.indicator, indicatorStyle]} />
@ -209,3 +216,29 @@ const TabItemInner = ({ tab, isSelected }: { tab: Tab; isSelected: boolean }) =>
</View>
)
}
const TarBarItem: FC<{
TabItem: FC<{ isSelected: boolean; tab: Tab } & Pick<TouchableOpacityProps, "onLayout">>
onTabItemPress: (index: number) => void
isSelected: boolean
tab: Tab
index: number
onLayout: (event: LayoutChangeEvent, index: number) => void
}> = memo(({ TabItem = Pressable, onTabItemPress, isSelected, tab, onLayout, index }) => {
return (
<TabItem
onPress={() => {
onTabItemPress(index)
}}
key={tab.value}
isSelected={isSelected}
onLayout={(event) => {
onLayout(event, index)
}}
tab={tab}
>
<TabItemInner tab={tab} isSelected={isSelected} />
</TabItem>
)
})

View File

@ -1,6 +1,6 @@
import { cn } from "@follow/utils"
import type { FC } from "react"
import { useCallback, useEffect, useRef, useState } from "react"
import { memo, useCallback, useEffect, useRef, useState } from "react"
import type { ScrollView, StyleProp, TouchableOpacityProps, ViewStyle } from "react-native"
import {
Animated as RnAnimated,
@ -117,10 +117,30 @@ export const TabView: FC<TabViewProps> = ({
>
{tabs.map((tab, index) => (
<View className="flex-1" style={{ width: windowWidth }} key={tab.value}>
{shouldRenderCurrentTab(index) && <Tab isSelected={currentTab === index} tab={tab} />}
{shouldRenderCurrentTab(index) && (
<TabComponent
key={tab.value}
tab={tab}
isSelected={currentTab === index}
Tab={Tab as TabComponent}
/>
)}
</View>
))}
</AnimatedScrollView>
</>
)
}
const TabComponent: FC<{
tab: Tab
isSelected: boolean
Tab: TabComponent
}> = memo(({ tab, isSelected, Tab = View }) => {
const { width: windowWidth } = useWindowDimensions()
return (
<View className="flex-1" style={{ width: windowWidth }} key={tab.value}>
<Tab isSelected={isSelected} tab={tab} />
</View>
)
})

View File

@ -6,7 +6,7 @@ import { useHeaderHeight } from "@react-navigation/elements"
import { FlashList } from "@shopify/flash-list"
import { useQuery } from "@tanstack/react-query"
import type { FC } from "react"
import { useCallback, useMemo, useRef } from "react"
import { memo, useCallback, useMemo, useRef } from "react"
import { Text, TouchableOpacity, View } from "react-native"
import type { PanGestureHandlerGestureEvent } from "react-native-gesture-handler"
import { PanGestureHandler } from "react-native-gesture-handler"
@ -63,7 +63,7 @@ const fetchRsshubPopular = (category: DiscoverCategories, lang: Language) => {
})
}
const Tab: TabComponent = ({ tab }) => {
const Tab: TabComponent = ({ tab, ...rest }) => {
const tabHeight = useBottomTabBarHeight()
const { data, isLoading } = useQuery({
@ -150,7 +150,7 @@ const Tab: TabComponent = ({ tab }) => {
}
return (
<View className="bg-system-background flex-1">
<View className="bg-system-background flex-1" {...rest}>
<FlashList
estimatedItemSize={150}
ref={listRef}
@ -162,7 +162,6 @@ const Tab: TabComponent = ({ tab }) => {
contentContainerStyle={{ paddingBottom: tabHeight }}
removeClippedSubviews
/>
{/* Right Sidebar */}
<NavigationSidebar alphabetGroups={alphabetGroups} listRef={listRef} />
</View>
@ -194,7 +193,7 @@ const ItemRenderer = ({
const NavigationSidebar: FC<{
alphabetGroups: (string | { key: string; data: RSSHubRouteDeclaration })[]
listRef: React.RefObject<FlashList<string | { key: string; data: RSSHubRouteDeclaration }>>
}> = ({ alphabetGroups, listRef }) => {
}> = memo(({ alphabetGroups, listRef }) => {
const scrollToLetter = useCallback(
(letter: string, animated = true) => {
const index = alphabetGroups.findIndex((group) => {
@ -264,4 +263,4 @@ const NavigationSidebar: FC<{
</PanGestureHandler>
</View>
)
}
})