feat: optimize home screen data loading with pagination and refresh functionality

This commit is contained in:
imeepos
2026-01-26 17:33:21 +08:00
parent 23fae07a58
commit 818f6e8591
6 changed files with 391 additions and 107 deletions

View File

@@ -1,4 +1,4 @@
import React, { memo } from 'react'
import React, { memo, useCallback, useMemo } from 'react'
import { Pressable, StyleSheet, Text, View, ViewStyle } from 'react-native'
import { Image } from 'expo-image'
import { LinearGradient } from 'expo-linear-gradient'
@@ -41,6 +41,11 @@ export function getImageUri(
return webpPreviewUrl || previewUrl || coverImageUrl
}
// 渐变颜色常量,避免每次渲染创建新数组
const GRADIENT_COLORS = ['rgba(17, 17, 17, 0)', 'rgba(17, 17, 17, 0.9)'] as const
const GRADIENT_START = { x: 0, y: 0 }
const GRADIENT_END = { x: 0, y: 1 }
const TemplateCardComponent: React.FC<TemplateCardProps> = ({
id,
title,
@@ -52,8 +57,26 @@ const TemplateCardComponent: React.FC<TemplateCardProps> = ({
onPress,
testID,
}) => {
const aspectRatio = parseAspectRatio(aspectRatioString)
const imageUri = getImageUri(webpPreviewUrl, previewUrl, coverImageUrl)
const aspectRatio = useMemo(() => parseAspectRatio(aspectRatioString), [aspectRatioString])
const imageUri = useMemo(() => getImageUri(webpPreviewUrl, previewUrl, coverImageUrl), [webpPreviewUrl, previewUrl, coverImageUrl])
// 使用 useCallback 缓存 onPress 回调
const handlePress = useCallback(() => {
if (id) {
onPress(id)
}
}, [id, onPress])
// 缓存样式计算
const cardStyle = useMemo(() => [styles.card, { width: cardWidth }], [cardWidth])
const containerStyle = useMemo(() =>
[styles.cardImageContainer, aspectRatio ? { aspectRatio } : undefined].filter(Boolean) as ViewStyle[],
[aspectRatio]
)
const imageStyle = useMemo(() =>
[styles.cardImage, aspectRatio ? { aspectRatio } : undefined].filter(Boolean) as ViewStyle[],
[aspectRatio]
)
// 如果没有 id则不渲染卡片
if (!id) {
@@ -62,28 +85,23 @@ const TemplateCardComponent: React.FC<TemplateCardProps> = ({
return (
<Pressable
style={[styles.card, { width: cardWidth }]}
onPress={() => onPress(id)}
style={cardStyle}
onPress={handlePress}
testID={testID}
>
<View
style={[
styles.cardImageContainer,
aspectRatio ? { aspectRatio } : undefined,
].filter(Boolean) as ViewStyle[]}
>
<View style={containerStyle}>
<Image
source={{ uri: imageUri }}
style={[
styles.cardImage,
aspectRatio ? { aspectRatio } : undefined,
].filter(Boolean) as ViewStyle[]}
style={imageStyle}
contentFit="cover"
cachePolicy="memory-disk"
recyclingKey={id}
transition={200}
/>
<LinearGradient
colors={['rgba(17, 17, 17, 0)', 'rgba(17, 17, 17, 0.9)']}
start={{ x: 0, y: 0 }}
end={{ x: 0, y: 1 }}
colors={GRADIENT_COLORS}
start={GRADIENT_START}
end={GRADIENT_END}
style={styles.cardImageGradient}
/>
<Text style={styles.cardTitle} numberOfLines={1}>

View File

@@ -1,12 +1,13 @@
import React, { useState, memo } from 'react'
import { View, StyleSheet, LayoutChangeEvent } from 'react-native'
import type { CategoryTemplate } from '@repo/sdk'
import type { CategoryTemplate, TemplateDetail } from '@repo/sdk'
import { TemplateCard } from './TemplateCard'
export type Template = CategoryTemplate
// 支持 CategoryTemplate 和 TemplateDetail 两种类型
export type Template = CategoryTemplate | TemplateDetail
export interface TemplateGridProps {
templates: CategoryTemplate[]
templates: Template[]
onTemplatePress: (id: string) => void
numColumns?: number
horizontalPadding?: number
@@ -40,7 +41,7 @@ const TemplateGridComponent: React.FC<TemplateGridProps> = ({
const [gridWidth, setGridWidth] = useState(0)
// 过滤掉没有 id 的模板
const validTemplates = templates.filter((template): template is CategoryTemplate & { id: string } =>
const validTemplates = templates.filter((template): template is Template & { id: string } =>
!!template.id
)