feat: 使用 useIsFocused hook 优化组件渲染,页面失焦时不渲染列表项以减少内存占用

This commit is contained in:
康猛 2026-01-19 15:38:29 +08:00
parent 912c380a8a
commit ef4114f694
4 changed files with 39 additions and 9 deletions

View File

@ -1,4 +1,5 @@
import { FontAwesome, Fontisto, Ionicons } from '@expo/vector-icons' import { FontAwesome, Fontisto, Ionicons } from '@expo/vector-icons'
import { useIsFocused } from '@react-navigation/native'
import { FlashList } from '@shopify/flash-list' import { FlashList } from '@shopify/flash-list'
import { useFocusEffect, useRouter } from 'expo-router' import { useFocusEffect, useRouter } from 'expo-router'
import { observer } from 'mobx-react-lite' import { observer } from 'mobx-react-lite'
@ -42,8 +43,11 @@ const Generate = observer(function Generate() {
const templates = useTemplates() const templates = useTemplates()
const { runTemplate } = useTemplateActions() const { runTemplate } = useTemplateActions()
// 直接使用 useIsFocused hook页面失焦时不渲染列表项以减少内存占用
const isFocused = useIsFocused()
useFocusEffect(() => { useFocusEffect(() => {
if (!isAuthenticated) { if (!isAuthenticated && isFocused) {
router.replace('/') router.replace('/')
router.push('/auth') router.push('/auth')
} }
@ -253,11 +257,13 @@ const Generate = observer(function Generate() {
item={item} item={item}
key={item?.id} key={item?.id}
itemWidth={itemWidth} itemWidth={itemWidth}
// 页面失焦时不渲染,减少内存占用
isVisible={isFocused}
onSelect={() => handleSelectTemplate(item)} onSelect={() => handleSelectTemplate(item)}
/> />
) )
}, },
[selectedTemplateId, itemWidth, handleSelectTemplate], [selectedTemplateId, itemWidth, handleSelectTemplate, isFocused],
) )
const ListHeader = useMemo(() => { const ListHeader = useMemo(() => {
@ -457,9 +463,16 @@ type TemplateItemProps = {
item: Template item: Template
itemWidth: number itemWidth: number
isSelected: boolean isSelected: boolean
isVisible?: boolean
onSelect: () => void onSelect: () => void
} }
const TemplateItem = memo<TemplateItemProps>(function TemplateItem({ item, itemWidth, isSelected, onSelect }) { const TemplateItem = memo<TemplateItemProps>(function TemplateItem({
item,
itemWidth,
isSelected,
isVisible = true,
onSelect,
}) {
return ( return (
<Block <Block
key={item?.id} key={item?.id}
@ -475,7 +488,7 @@ const TemplateItem = memo<TemplateItemProps>(function TemplateItem({ item, itemW
onClick={onSelect} onClick={onSelect}
> >
{/* <Img className="size-full" contentFit="cover" src={item.image} /> */} {/* <Img className="size-full" contentFit="cover" src={item.image} /> */}
<VideoBox style={{ width: itemWidth, height: itemWidth }} url={item.videoUrl} /> {isVisible && <VideoBox style={{ width: itemWidth, height: itemWidth }} url={item.videoUrl} />}
{isSelected && <Block className="absolute inset-0 bg-black/60" />} {isSelected && <Block className="absolute inset-0 bg-black/60" />}
{isSelected && <Block className="absolute inset-[-5px] border-[3px] border-accent" />} {isSelected && <Block className="absolute inset-[-5px] border-[3px] border-accent" />}

View File

@ -1,4 +1,5 @@
import { Ionicons } from '@expo/vector-icons' import { Ionicons } from '@expo/vector-icons'
import { useIsFocused } from '@react-navigation/native'
import { Block, ConfirmModal, Img, ListEmpty, Text, Toast, VideoBox } from '@share/components' import { Block, ConfirmModal, Img, ListEmpty, Text, Toast, VideoBox } from '@share/components'
import { FlashList } from '@shopify/flash-list' import { FlashList } from '@shopify/flash-list'
import { LinearGradient } from 'expo-linear-gradient' import { LinearGradient } from 'expo-linear-gradient'
@ -54,6 +55,9 @@ const Index = observer(function Index() {
const [allItems, setAllItems] = useState<MediaItem[]>([]) const [allItems, setAllItems] = useState<MediaItem[]>([])
const [selectedItem, setSelectedItem] = useState<MediaItem | null>(null) const [selectedItem, setSelectedItem] = useState<MediaItem | null>(null)
// 直接使用 useIsFocused hook无需手动管理状态
const isFocused = useIsFocused()
const [refreshing, setRefreshing] = useState(false) const [refreshing, setRefreshing] = useState(false)
const [loadingMore, setLoadingMore] = useState(false) const [loadingMore, setLoadingMore] = useState(false)
const [hasMore, setHasMore] = useState(true) const [hasMore, setHasMore] = useState(true)
@ -391,11 +395,12 @@ const Index = observer(function Index() {
isSelected={selectedId === item.id} isSelected={selectedId === item.id}
item={item} item={item}
itemWidth={ITEM_WIDTH} itemWidth={ITEM_WIDTH}
isVisible={index < 9 || visibleIdsRef.current.has(item.id)} // 页面失焦时不渲染,减少内存占用
isVisible={isFocused && (index < 9 || visibleIdsRef.current.has(item.id))}
onSelect={() => setSelectedItem(item)} onSelect={() => setSelectedItem(item)}
/> />
), ),
[selectedId], [selectedId, isFocused],
) )
const keyExtractor = useCallback((item: MediaItem) => item.id, []) const keyExtractor = useCallback((item: MediaItem) => item.id, [])

View File

@ -1,4 +1,5 @@
import { AntDesign, EvilIcons, FontAwesome, Ionicons, MaterialCommunityIcons } from '@expo/vector-icons' import { AntDesign, EvilIcons, FontAwesome, Ionicons, MaterialCommunityIcons } from '@expo/vector-icons'
import { useIsFocused } from '@react-navigation/native'
import { Block, ConfirmModal, Img, Input, ListEmpty, Text, Toast, VideoBox } from '@share/components' import { Block, ConfirmModal, Img, Input, ListEmpty, Text, Toast, VideoBox } from '@share/components'
import { FlashList } from '@shopify/flash-list' import { FlashList } from '@shopify/flash-list'
import * as ImagePicker from 'expo-image-picker' import * as ImagePicker from 'expo-image-picker'
@ -47,6 +48,9 @@ const Sync = observer(() => {
const [isSelectionMode, setIsSelectionMode] = useState(false) const [isSelectionMode, setIsSelectionMode] = useState(false)
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set()) const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set())
// 直接使用 useIsFocused hook页面失焦时不渲染列表项以减少内存占用
const isFocused = useIsFocused()
const { connectedDevice, isScanning, transferProgress } = bleStore.state const { connectedDevice, isScanning, transferProgress } = bleStore.state
const itemWidth = Math.floor((screenWidth - 12 * 2 - 12 * 2) / 3) const itemWidth = Math.floor((screenWidth - 12 * 2 - 12 * 2) / 3)
@ -380,16 +384,18 @@ const Sync = observer(() => {
isSelectionMode={isSelectionMode} isSelectionMode={isSelectionMode}
itemWidth={itemWidth} itemWidth={itemWidth}
post={post} post={post}
// 页面失焦时不渲染,减少内存占用
isVisible={isFocused}
onSelect={handleItemSelect} onSelect={handleItemSelect}
key={post?.id} key={post?.id}
/> />
) )
}, },
[isSelectionMode, selectedIds, selectedItem, itemWidth, handleItemSelect], [isSelectionMode, selectedIds, selectedItem, itemWidth, handleItemSelect, isFocused],
) )
return ( return (
<Block className="relative flex-1"> <Block className="relative flex-1 bg-black">
<BannerSection /> <BannerSection />
<Block className="z-10 flex-1"> <Block className="z-10 flex-1">
@ -658,12 +664,14 @@ const GridItem = memo(
isSelected, isSelected,
isSelectionMode, isSelectionMode,
itemWidth, itemWidth,
isVisible = true,
onSelect, onSelect,
}: { }: {
post: any post: any
isSelected: boolean isSelected: boolean
isSelectionMode: boolean isSelectionMode: boolean
itemWidth: number itemWidth: number
isVisible?: boolean
onSelect: (post: any) => void onSelect: (post: any) => void
}) => { }) => {
// 渲染状态标记 // 渲染状态标记
@ -707,7 +715,7 @@ const GridItem = memo(
style={{ transform: [{ skewX: '-6deg' }], height: itemWidth, width: itemWidth }} style={{ transform: [{ skewX: '-6deg' }], height: itemWidth, width: itemWidth }}
> >
<Block style={{ height: itemWidth, width: itemWidth }}> <Block style={{ height: itemWidth, width: itemWidth }}>
<Img className="size-full" src={post.imageUrl} /> {isVisible && <Img className="size-full" src={post.imageUrl} />}
</Block> </Block>
{isSelected && <Block className="absolute inset-0 border-[3px] border-accent" />} {isSelected && <Block className="absolute inset-0 border-[3px] border-accent" />}

View File

@ -1,3 +1,4 @@
import { useIsFocused } from '@react-navigation/native'
import { memo, useEffect, useState } from 'react' import { memo, useEffect, useState } from 'react'
import Svg, { Circle, Defs, Pattern, Rect } from 'react-native-svg' import Svg, { Circle, Defs, Pattern, Rect } from 'react-native-svg'
@ -19,6 +20,9 @@ const BannerSection = memo<BannerProps>(function Banner({ bgVideo }) {
setBgurl(bgVideo) setBgurl(bgVideo)
}, [bgVideo]) }, [bgVideo])
const isFocused = useIsFocused()
if (!isFocused) return null
return ( return (
<Block className="absolute inset-0 z-0 overflow-hidden"> <Block className="absolute inset-0 z-0 overflow-hidden">
{/* 使用较小的分辨率降低内存占用 */} {/* 使用较小的分辨率降低内存占用 */}