refactor(PostCard, PostCardGrid, HomeScreen): enhance PostCard component and remove PostCardGrid
- Introduced a new PostCardRelativeTime component for dynamic time display, improving user experience with real-time updates. - Refactored PostCard to utilize memoization for performance optimization and prevent unnecessary re-renders. - Removed the PostCardGrid component to streamline the codebase, consolidating functionality within the PostCard component. - Updated HomeScreen to leverage the new PostCard features, ensuring consistent post rendering and interaction handling.
This commit is contained in:
@@ -12,8 +12,8 @@
|
||||
* />
|
||||
*/
|
||||
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { View, TouchableOpacity, StyleSheet, Alert } from 'react-native';
|
||||
import React, { useMemo, useState, memo, useEffect } from 'react';
|
||||
import { View, TouchableOpacity, StyleSheet, Alert, TextStyle } from 'react-native';
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
import { PostCardProps, PostCardAction } from './types';
|
||||
import { ImageGridItem, SmartImage } from '../../common';
|
||||
@@ -23,12 +23,95 @@ import { colors, spacing, borderRadius, fontSizes } from '../../../theme';
|
||||
import { getPreviewImageUrl } from '../../../utils/imageHelper';
|
||||
import PostImages from './components/PostImages';
|
||||
import { useResponsive } from '../../../hooks/useResponsive';
|
||||
import { formatPostCreatedAtString } from '../../../core/entities/Post';
|
||||
|
||||
/** 列表相对时间:独立定时刷新,避免外层 PostCard.memo 挡住「分钟前」更新 */
|
||||
const PostCardRelativeTime: React.FC<{ createdAt?: string | null; style?: TextStyle | TextStyle[] }> = ({
|
||||
createdAt,
|
||||
style: timeStyle,
|
||||
}) => {
|
||||
const [label, setLabel] = useState(() => formatPostCreatedAtString(createdAt));
|
||||
|
||||
useEffect(() => {
|
||||
setLabel(formatPostCreatedAtString(createdAt));
|
||||
const tick = () => setLabel(formatPostCreatedAtString(createdAt));
|
||||
const id = setInterval(tick, 60_000);
|
||||
return () => clearInterval(id);
|
||||
}, [createdAt]);
|
||||
|
||||
return (
|
||||
<Text style={timeStyle} numberOfLines={1}>
|
||||
{label}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
function imagesSignature(
|
||||
images: PostCardProps['post']['images'] | undefined
|
||||
): string {
|
||||
if (!images?.length) return '';
|
||||
return images.map((i) => i.id).join('\u001f');
|
||||
}
|
||||
|
||||
function authorSignature(author: PostCardProps['post']['author']): string {
|
||||
if (!author) return '';
|
||||
return [author.id, author.nickname ?? '', author.avatar ?? ''].join('\u001f');
|
||||
}
|
||||
|
||||
function topCommentSignature(tc: PostCardProps['post']['top_comment']): string {
|
||||
if (!tc) return '';
|
||||
return [tc.id, tc.content ?? '', tc.author?.id ?? ''].join('\u001f');
|
||||
}
|
||||
|
||||
function featuresComparable(f: PostCardProps['features']): string {
|
||||
if (f == null) return '';
|
||||
if (typeof f === 'string') return f;
|
||||
return JSON.stringify(f);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义比较:忽略 onAction 引用(父组件应用 postId + ref 解析最新 post,避免 Tab 切换时全列表因回调重建而重绘)。
|
||||
* index 未参与 UI,不比较以免列表重排时多余更新。
|
||||
*/
|
||||
function arePostCardPropsEqual(prev: PostCardProps, next: PostCardProps): boolean {
|
||||
if (prev.variant !== next.variant) return false;
|
||||
if (prev.isPostAuthor !== next.isPostAuthor) return false;
|
||||
if (prev.isAuthor !== next.isAuthor) return false;
|
||||
if (prev.style !== next.style) return false;
|
||||
if (featuresComparable(prev.features) !== featuresComparable(next.features)) return false;
|
||||
|
||||
const a = prev.post;
|
||||
const b = next.post;
|
||||
if (a === b) return true;
|
||||
|
||||
if (a.id !== b.id) return false;
|
||||
if (a.title !== b.title) return false;
|
||||
if (a.content !== b.content) return false;
|
||||
if ((a.likes_count ?? 0) !== (b.likes_count ?? 0)) return false;
|
||||
if ((a.comments_count ?? 0) !== (b.comments_count ?? 0)) return false;
|
||||
if ((a.favorites_count ?? 0) !== (b.favorites_count ?? 0)) return false;
|
||||
if ((a.shares_count ?? 0) !== (b.shares_count ?? 0)) return false;
|
||||
if ((a.views_count ?? 0) !== (b.views_count ?? 0)) return false;
|
||||
if (!!a.is_pinned !== !!b.is_pinned) return false;
|
||||
if (!!a.is_locked !== !!b.is_locked) return false;
|
||||
if (!!a.is_vote !== !!b.is_vote) return false;
|
||||
if (a.created_at !== b.created_at) return false;
|
||||
if (a.updated_at !== b.updated_at) return false;
|
||||
if (a.content_edited_at !== b.content_edited_at) return false;
|
||||
if (!!a.is_liked !== !!b.is_liked) return false;
|
||||
if (!!a.is_favorited !== !!b.is_favorited) return false;
|
||||
if (authorSignature(a.author) !== authorSignature(b.author)) return false;
|
||||
if (imagesSignature(a.images) !== imagesSignature(b.images)) return false;
|
||||
if (topCommentSignature(a.top_comment) !== topCommentSignature(b.top_comment)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* PostCard 主组件
|
||||
* 仅支持新 API
|
||||
*/
|
||||
const PostCard: React.FC<PostCardProps> = (normalizedProps) => {
|
||||
const PostCardInner: React.FC<PostCardProps> = (normalizedProps) => {
|
||||
const {
|
||||
post,
|
||||
onAction,
|
||||
@@ -247,9 +330,7 @@ const PostCard: React.FC<PostCardProps> = (normalizedProps) => {
|
||||
<Text style={styles.authorName} numberOfLines={1}>{author?.nickname || '匿名用户'}</Text>
|
||||
{!showGrid && (
|
||||
<View style={styles.metaRow}>
|
||||
<Text style={styles.timeText} numberOfLines={1}>
|
||||
{post.created_at ? new Date(post.created_at).toLocaleDateString() : ''}
|
||||
</Text>
|
||||
<PostCardRelativeTime createdAt={post.created_at} style={styles.timeText} />
|
||||
{post.is_pinned && (
|
||||
<View style={styles.pinnedTag}>
|
||||
<MaterialCommunityIcons name="pin" size={10} color={colors.warning.main} />
|
||||
@@ -334,6 +415,10 @@ const PostCard: React.FC<PostCardProps> = (normalizedProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
PostCardInner.displayName = 'PostCard';
|
||||
|
||||
const PostCard = memo(PostCardInner, arePostCardPropsEqual);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
card: {
|
||||
backgroundColor: colors.background.paper,
|
||||
|
||||
Reference in New Issue
Block a user