refactor: migrate post operations to use case architecture with differential updates
Some checks failed
Frontend CI / build-and-push-web (push) Failing after 3m11s
Frontend CI / ota-android (push) Failing after 16m3s
Frontend CI / build-android-apk (push) Has been cancelled

Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
This commit is contained in:
lafay
2026-03-21 20:55:36 +08:00
parent 25071d2303
commit d273569911
30 changed files with 4395 additions and 572 deletions

View File

@@ -0,0 +1,430 @@
/**
* 帖子差异计算器
* 计算两个帖子列表之间的差异,支持增量更新
*/
import {
PostIdentifier,
PostDiffResult,
PostDiffConfig,
DEFAULT_POST_DIFF_CONFIG,
PostComparator,
PostSorter,
PostDiffStats,
} from './postTypes';
/**
* 帖子差异计算器类
* 用于高效计算帖子列表的变化
*/
export class PostDiffCalculator<T extends PostIdentifier> {
private config: Required<PostDiffConfig>;
private previousPosts: Map<string, T> = new Map();
private previousOrder: string[] = [];
private stats: PostDiffStats = {
totalBatches: 0,
totalUpdates: 0,
averageBatchSize: 0,
lastBatchTime: 0,
addedCount: 0,
updatedCount: 0,
deletedCount: 0,
};
constructor(config: PostDiffConfig = {}) {
this.config = { ...DEFAULT_POST_DIFF_CONFIG, ...config };
}
/**
* 计算两个帖子列表的差异
* @param oldList 旧帖子列表
* @param newList 新帖子列表
* @returns 差异结果
*/
calculateDiff(oldList: T[], newList: T[]): PostDiffResult<T> {
const result: PostDiffResult<T> = {
added: [],
updated: [],
deleted: [],
moved: [],
unchanged: [],
shouldReset: false,
};
// 如果旧列表为空,直接返回新列表作为新增
if (oldList.length === 0) {
result.added = [...newList];
this.updateCache(newList);
this.updateStats(0, newList.length, 0);
return result;
}
// 如果新列表为空,表示全部删除
if (newList.length === 0) {
result.deleted = oldList.map((post, index) => ({ post, index }));
this.clearCache();
this.updateStats(0, 0, oldList.length);
return result;
}
// 检查是否需要重置(变化太大)
const changeRatio = this.calculateChangeRatio(oldList, newList);
if (changeRatio > this.config.resetThreshold) {
result.shouldReset = true;
result.added = [...newList];
this.updateCache(newList);
this.updateStats(0, newList.length, 0);
return result;
}
// 构建旧列表的索引映射
const oldIndexMap = new Map<string, number>();
oldList.forEach((post, index) => {
oldIndexMap.set(post.id, index);
});
// 构建新列表的索引映射
const newIndexMap = new Map<string, number>();
newList.forEach((post, index) => {
newIndexMap.set(post.id, index);
});
// 检测新增、更新和未变更的帖子
const processedIds = new Set<string>();
let addedCount = 0;
let updatedCount = 0;
for (let newIndex = 0; newIndex < newList.length; newIndex++) {
const newPost = newList[newIndex];
const oldIndex = oldIndexMap.get(newPost.id);
if (oldIndex === undefined) {
// 新增帖子
result.added.push(newPost);
addedCount++;
} else {
const oldPost = oldList[oldIndex];
processedIds.add(newPost.id);
// 检测是否有更新
const changes = this.detectChanges(oldPost, newPost);
if (changes && Object.keys(changes).length > 0) {
result.updated.push({
post: newPost,
index: oldIndex,
changes,
});
updatedCount++;
} else {
result.unchanged.push(newPost);
}
// 检测是否移动
if (oldIndex !== newIndex) {
result.moved.push({
post: newPost,
fromIndex: oldIndex,
toIndex: newIndex,
});
}
}
}
// 检测删除的帖子
let deletedCount = 0;
for (let oldIndex = 0; oldIndex < oldList.length; oldIndex++) {
const oldPost = oldList[oldIndex];
if (!processedIds.has(oldPost.id)) {
result.deleted.push({ post: oldPost, index: oldIndex });
deletedCount++;
}
}
this.updateCache(newList);
this.updateStats(addedCount, updatedCount, deletedCount);
return result;
}
/**
* 计算增量差异(基于缓存的上次状态)
* @param newList 新帖子列表
* @returns 差异结果
*/
calculateIncrementalDiff(newList: T[]): PostDiffResult<T> {
const oldList = Array.from(this.previousPosts.values());
// 按照之前的顺序排序
oldList.sort((a, b) => {
const indexA = this.previousOrder.indexOf(a.id);
const indexB = this.previousOrder.indexOf(b.id);
return indexA - indexB;
});
return this.calculateDiff(oldList, newList);
}
/**
* 计算单个帖子的变化
* @param oldPost 旧帖子
* @param newPost 新帖子
* @returns 变化的字段
*/
detectChanges(oldPost: T, newPost: T): Partial<T> | null {
const changes: Partial<T> = {};
let hasChanges = false;
// 如果配置了追踪特定字段,只检测这些字段
const fieldsToCheck = this.config.detectPartialUpdates
? this.config.trackedFields
: (Object.keys(newPost) as Array<keyof T>);
for (const key of fieldsToCheck) {
if (key === 'id') continue; // ID 不变
const oldValue = (oldPost as any)[key];
const newValue = (newPost as any)[key];
// 深度比较
if (this.hasValueChanged(oldValue, newValue)) {
(changes as any)[key] = newValue;
hasChanges = true;
}
}
return hasChanges ? changes : null;
}
/**
* 检测值是否变化
* @param oldValue 旧值
* @param newValue 新值
* @returns 是否变化
*/
private hasValueChanged(oldValue: any, newValue: any): boolean {
// 简单类型直接比较
if (typeof oldValue !== 'object' || oldValue === null || newValue === null) {
return oldValue !== newValue;
}
// 数组比较
if (Array.isArray(oldValue) && Array.isArray(newValue)) {
if (oldValue.length !== newValue.length) return true;
return JSON.stringify(oldValue) !== JSON.stringify(newValue);
}
// 对象比较
return JSON.stringify(oldValue) !== JSON.stringify(newValue);
}
/**
* 计算变化比例
* @param oldList 旧列表
* @param newList 新列表
* @returns 变化比例0-1
*/
private calculateChangeRatio(oldList: T[], newList: T[]): number {
if (oldList.length === 0 && newList.length === 0) return 0;
if (oldList.length === 0 || newList.length === 0) return 1;
const oldIds = new Set(oldList.map(p => p.id));
const newIds = new Set(newList.map(p => p.id));
let commonCount = 0;
oldIds.forEach(id => {
if (newIds.has(id)) {
commonCount++;
}
});
const maxLength = Math.max(oldList.length, newList.length);
return 1 - commonCount / maxLength;
}
/**
* 更新缓存
* @param posts 帖子列表
*/
private updateCache(posts: T[]): void {
this.previousPosts.clear();
this.previousOrder = [];
for (const post of posts) {
this.previousPosts.set(post.id, post);
this.previousOrder.push(post.id);
}
}
/**
* 清除缓存
*/
private clearCache(): void {
this.previousPosts.clear();
this.previousOrder = [];
}
/**
* 更新统计信息
*/
private updateStats(addedCount: number, updatedCount: number, deletedCount: number): void {
this.stats.totalBatches++;
const totalChanges = addedCount + updatedCount + deletedCount;
this.stats.totalUpdates += totalChanges;
this.stats.averageBatchSize = this.stats.totalUpdates / this.stats.totalBatches;
this.stats.lastBatchTime = Date.now();
this.stats.addedCount += addedCount;
this.stats.updatedCount += updatedCount;
this.stats.deletedCount += deletedCount;
}
/**
* 设置配置
* @param config 配置
*/
setConfig(config: Partial<PostDiffConfig>): void {
this.config = { ...this.config, ...config };
}
/**
* 获取当前配置
* @returns 当前配置
*/
getConfig(): Required<PostDiffConfig> {
return { ...this.config };
}
/**
* 重置计算器状态
*/
reset(): void {
this.clearCache();
this.stats = {
totalBatches: 0,
totalUpdates: 0,
averageBatchSize: 0,
lastBatchTime: 0,
addedCount: 0,
updatedCount: 0,
deletedCount: 0,
};
}
/**
* 获取缓存的帖子数量
* @returns 缓存数量
*/
getCachedCount(): number {
return this.previousPosts.size;
}
/**
* 检查帖子是否在缓存中
* @param postId 帖子 ID
* @returns 是否存在
*/
hasPost(postId: string): boolean {
return this.previousPosts.has(postId);
}
/**
* 获取缓存中的帖子
* @param postId 帖子 ID
* @returns 帖子或 undefined
*/
getCachedPost(postId: string): T | undefined {
return this.previousPosts.get(postId);
}
/**
* 获取统计信息
* @returns 统计信息
*/
getStats(): PostDiffStats {
return { ...this.stats };
}
/**
* 批量检测帖子变化
* @param posts 要检测的帖子列表
* @returns 变化的帖子列表
*/
batchDetectChanges(posts: T[]): Array<{ post: T; changes: Partial<T> }> {
const results: Array<{ post: T; changes: Partial<T> }> = [];
for (const post of posts) {
const cachedPost = this.previousPosts.get(post.id);
if (cachedPost) {
const changes = this.detectChanges(cachedPost, post);
if (changes && Object.keys(changes).length > 0) {
results.push({ post, changes });
}
}
}
return results;
}
/**
* 比较两个帖子列表是否相同
* @param list1 列表1
* @param list2 列表2
* @returns 是否相同
*/
areEqual(list1: T[], list2: T[]): boolean {
if (list1.length !== list2.length) return false;
for (let i = 0; i < list1.length; i++) {
if (list1[i].id !== list2[i].id) return false;
}
return true;
}
/**
* 获取两个帖子列表的交集ID
* @param list1 列表1
* @param list2 列表2
* @returns 交集ID集合
*/
getIntersection(list1: T[], list2: T[]): Set<string> {
const ids1 = new Set(list1.map(p => p.id));
const ids2 = new Set(list2.map(p => p.id));
const intersection = new Set<string>();
ids1.forEach(id => {
if (ids2.has(id)) {
intersection.add(id);
}
});
return intersection;
}
/**
* 获取两个帖子列表的差集ID
* @param list1 列表1
* @param list2 列表2
* @returns 差集ID集合在list1中但不在list2中
*/
getDifference(list1: T[], list2: T[]): Set<string> {
const ids1 = new Set(list1.map(p => p.id));
const ids2 = new Set(list2.map(p => p.id));
const difference = new Set<string>();
ids1.forEach(id => {
if (!ids2.has(id)) {
difference.add(id);
}
});
return difference;
}
}
/**
* 创建帖子差异计算器实例的工厂函数
* @param config 配置
* @returns PostDiffCalculator 实例
*/
export function createPostDiffCalculator<T extends PostIdentifier>(
config?: PostDiffConfig
): PostDiffCalculator<T> {
return new PostDiffCalculator<T>(config);
}

View File

@@ -0,0 +1,505 @@
/**
* 帖子更新批量处理器
* 收集更新操作并按批处理,减少 UI 更新次数
*/
import {
PostUpdate,
PostUpdateType,
PostBatchUpdateListener,
PostDiffConfig,
DEFAULT_POST_DIFF_CONFIG,
PostIdentifier,
PostUpdateBatch,
} from './postTypes';
/**
* 批量处理器配置选项
*/
export interface PostBatcherOptions {
/** 批量处理间隔(毫秒),默认 16ms约 60fps */
batchInterval?: number;
/** 最大批量大小,默认 50 */
maxBatchSize?: number;
/** 是否启用去重,默认 true */
enableDeduplication?: boolean;
/** 是否合并相同帖子的多次更新,默认 true */
enableMerge?: boolean;
/** 最大等待时间(毫秒),超过此时间强制刷新,默认 100ms */
maxWaitTime?: number;
/** 是否自动启动批量处理,默认 true */
autoStart?: boolean;
}
/**
* 默认批量处理器配置
*/
export const DEFAULT_POST_BATCHER_OPTIONS: Required<PostBatcherOptions> = {
batchInterval: 16,
maxBatchSize: 50,
enableDeduplication: true,
enableMerge: true,
maxWaitTime: 100,
autoStart: true,
};
/**
* 帖子更新批量处理器类
*/
export class PostUpdateBatcher {
/** 待处理的更新队列 */
private pendingUpdates: Map<string, PostUpdate> = new Map();
/** 批量处理定时器 */
private batchTimer: ReturnType<typeof setTimeout> | null = null;
/** 最大等待时间定时器 */
private maxWaitTimer: ReturnType<typeof setTimeout> | null = null;
/** 监听器集合 */
private listeners: Set<PostBatchUpdateListener> = new Set();
/** 是否正在批处理中 */
private isProcessing: boolean = false;
/** 配置选项 */
private options: Required<PostBatcherOptions>;
/** 处理器是否已启动 */
private isStarted: boolean = false;
/** 批量处理统计信息 */
private stats = {
totalBatches: 0,
totalUpdates: 0,
averageBatchSize: 0,
lastBatchTime: 0,
};
/** 批次 ID 计数器 */
private batchIdCounter: number = 0;
constructor(options: PostBatcherOptions = {}) {
this.options = { ...DEFAULT_POST_BATCHER_OPTIONS, ...options };
if (this.options.autoStart) {
this.start();
}
}
/**
* 启动批量处理器
*/
start(): void {
if (this.isStarted) return;
this.isStarted = true;
this.scheduleBatch();
}
/**
* 停止批量处理器
*/
stop(): void {
this.isStarted = false;
this.clearTimers();
}
/**
* 添加更新操作到批量队列
* @param update 帖子更新操作
*/
addUpdate(update: PostUpdate): void {
const key = this.generateUpdateKey(update);
// 去重:如果已有相同帖子 ID 的待处理更新,进行合并
if (this.options.enableDeduplication && this.pendingUpdates.has(key)) {
const existing = this.pendingUpdates.get(key)!;
if (this.options.enableMerge) {
const merged = this.mergeUpdates(existing, update);
if (merged) {
this.pendingUpdates.set(key, merged);
return;
}
}
}
this.pendingUpdates.set(key, update);
// 如果达到最大批量大小,立即刷新
if (this.pendingUpdates.size >= this.options.maxBatchSize) {
this.flush();
}
}
/**
* 添加多个更新操作
* @param updates 更新操作数组
*/
addUpdates(updates: PostUpdate[]): void {
for (const update of updates) {
this.addUpdate(update);
}
}
/**
* 立即刷新所有待处理更新
* @returns 处理的更新数组
*/
flush(): PostUpdate[] {
if (this.pendingUpdates.size === 0 || this.isProcessing) {
return [];
}
this.isProcessing = true;
this.clearTimers();
const updates = Array.from(this.pendingUpdates.values());
this.pendingUpdates.clear();
// 更新统计信息
this.stats.totalBatches++;
this.stats.totalUpdates += updates.length;
this.stats.averageBatchSize = this.stats.totalUpdates / this.stats.totalBatches;
this.stats.lastBatchTime = Date.now();
// 通知监听器
this.notifyListeners(updates);
this.isProcessing = false;
// 重新调度定时器
this.scheduleBatch();
return updates;
}
/**
* 刷新并返回批次对象
* @returns 批次对象或 null
*/
flushAsBatch(): PostUpdateBatch | null {
const updates = this.flush();
if (updates.length === 0) return null;
return {
batchId: `batch_${++this.batchIdCounter}_${Date.now()}`,
updates,
createdAt: Date.now(),
processed: false,
};
}
/**
* 订阅批量更新事件
* @param listener 监听器函数
* @returns 取消订阅函数
*/
subscribe(listener: PostBatchUpdateListener): () => void {
this.listeners.add(listener);
return () => {
this.listeners.delete(listener);
};
}
/**
* 取消订阅
* @param listener 监听器函数
*/
unsubscribe(listener: PostBatchUpdateListener): void {
this.listeners.delete(listener);
}
/**
* 获取待处理更新数量
* @returns 待处理数量
*/
getPendingCount(): number {
return this.pendingUpdates.size;
}
/**
* 获取统计信息
* @returns 统计信息
*/
getStats() {
return { ...this.stats };
}
/**
* 检查是否有待处理更新
* @returns 是否有待处理更新
*/
hasPendingUpdates(): boolean {
return this.pendingUpdates.size > 0;
}
/**
* 清空所有待处理更新
*/
clearPending(): void {
this.pendingUpdates.clear();
this.clearTimers();
}
/**
* 销毁批量处理器
*/
destroy(): void {
this.stop();
this.clearPending();
this.listeners.clear();
}
/**
* 生成更新键值
* @param update 更新操作
* @returns 键值
*/
private generateUpdateKey(update: PostUpdate): string {
// 根据更新类型生成唯一键
const updateType = (update as PostUpdate).type;
switch (updateType) {
case PostUpdateType.ADD:
case PostUpdateType.BATCH_ADD:
return `${updateType}_${(update as any).timestamp}`;
case PostUpdateType.UPDATE:
case PostUpdateType.BATCH_UPDATE:
return `${updateType}_${this.extractPostId(update)}`;
case PostUpdateType.DELETE:
case PostUpdateType.BATCH_DELETE:
return `${updateType}_${this.extractPostId(update)}`;
case PostUpdateType.MOVE:
return `${updateType}_${(update as any).postId}`;
case PostUpdateType.RESET:
return `${updateType}_${(update as any).timestamp}`;
default:
// 兜底处理未知类型
return `unknown_${Date.now()}_${Math.random()}`;
}
}
/**
* 提取帖子 ID
* @param update 更新操作
* @returns 帖子 ID 或空字符串
*/
private extractPostId(update: PostUpdate): string {
const payload = (update as any).payload;
if (payload) {
if (payload.postId) return payload.postId;
if (payload.postIds) return payload.postIds.join(',');
}
// 直接从更新对象提取
if ((update as any).postId) return (update as any).postId;
if ((update as any).postIds) return (update as any).postIds.join(',');
return '';
}
/**
* 合并更新操作
* @param existing 已有更新
* @param incoming 新更新
* @returns 合并后的更新或 null如果无法合并
*/
private mergeUpdates(existing: PostUpdate, incoming: PostUpdate): PostUpdate | null {
// 只有同类型的更新才能合并
if (existing.type !== incoming.type) {
return null;
}
switch (existing.type) {
case PostUpdateType.UPDATE:
// 合并更新字段
return {
...existing,
payload: {
...(existing as any).payload,
updates: {
...(existing as any).payload?.updates,
...(incoming as any).payload?.updates,
},
},
timestamp: incoming.timestamp,
};
case PostUpdateType.BATCH_UPDATE:
// 合并批量更新
const existingUpdates = (existing as any).payload?.updates || [];
const incomingUpdates = (incoming as any).payload?.updates || [];
const updatesMap = new Map<string, any>();
for (const update of [...existingUpdates, ...incomingUpdates]) {
const existing = updatesMap.get(update.postId);
if (existing) {
existing.changes = { ...existing.changes, ...update.changes };
} else {
updatesMap.set(update.postId, { ...update });
}
}
return {
...existing,
payload: {
...(existing as any).payload,
updates: Array.from(updatesMap.values()),
},
timestamp: incoming.timestamp,
};
case PostUpdateType.BATCH_DELETE:
// 合并删除 ID 列表
const existingIds = new Set((existing as any).payload?.postIds || []);
const incomingIds = (incoming as any).payload?.postIds || [];
for (const id of incomingIds) {
existingIds.add(id);
}
return {
...existing,
payload: {
...(existing as any).payload,
postIds: Array.from(existingIds),
},
timestamp: incoming.timestamp,
};
default:
return null;
}
}
/**
* 调度批量处理
*/
private scheduleBatch(): void {
if (!this.isStarted) return;
this.clearTimers();
// 设置最大等待时间定时器
this.maxWaitTimer = setTimeout(() => {
this.flush();
}, this.options.maxWaitTime);
// 设置批量处理定时器
this.batchTimer = setTimeout(() => {
this.flush();
}, this.options.batchInterval);
}
/**
* 清除所有定时器
*/
private clearTimers(): void {
if (this.batchTimer) {
clearTimeout(this.batchTimer);
this.batchTimer = null;
}
if (this.maxWaitTimer) {
clearTimeout(this.maxWaitTimer);
this.maxWaitTimer = null;
}
}
/**
* 通知监听器
* @param updates 更新数组
*/
private notifyListeners(updates: PostUpdate[]): void {
if (this.listeners.size === 0) return;
// 转换为数组以支持迭代
const listenersArray = Array.from(this.listeners);
for (const listener of listenersArray) {
try {
listener(updates);
} catch (error) {
console.error('Error in post batch update listener:', error);
}
}
}
// ==================== 便捷方法 ====================
/**
* 添加帖子新增操作
*/
addPost(post: PostIdentifier, index?: number): void {
this.addUpdate({
type: PostUpdateType.ADD,
post,
index,
timestamp: Date.now(),
});
}
/**
* 添加帖子更新操作
*/
updatePost(postId: string, updates: Partial<PostIdentifier>): void {
this.addUpdate({
type: PostUpdateType.UPDATE,
postId,
updates,
timestamp: Date.now(),
});
}
/**
* 添加帖子删除操作
*/
deletePost(postId: string): void {
this.addUpdate({
type: PostUpdateType.DELETE,
postId,
timestamp: Date.now(),
});
}
/**
* 批量添加帖子
*/
batchAddPosts(posts: PostIdentifier[], startIndex?: number): void {
this.addUpdate({
type: PostUpdateType.BATCH_ADD,
posts,
startIndex,
timestamp: Date.now(),
});
}
/**
* 批量更新帖子
*/
batchUpdatePosts(updates: Array<{ postId: string; changes: Partial<PostIdentifier> }>): void {
this.addUpdate({
type: PostUpdateType.BATCH_UPDATE,
updates,
timestamp: Date.now(),
});
}
/**
* 批量删除帖子
*/
batchDeletePosts(postIds: string[]): void {
this.addUpdate({
type: PostUpdateType.BATCH_DELETE,
postIds,
timestamp: Date.now(),
});
}
/**
* 重置帖子列表
*/
resetPosts(posts: PostIdentifier[]): void {
this.addUpdate({
type: PostUpdateType.RESET,
posts,
timestamp: Date.now(),
});
}
}
/**
* 创建帖子批量处理器的工厂函数
* @param options 配置选项
* @returns PostUpdateBatcher 实例
*/
export function createPostUpdateBatcher(
options?: PostBatcherOptions
): PostUpdateBatcher {
return new PostUpdateBatcher(options);
}

View File

@@ -1,8 +1,10 @@
/**
* 差异更新模块
* 提供消息列表的增量更新功能,减少 UI 重渲染次数
* 提供消息列表和帖子列表的增量更新功能,减少 UI 重渲染次数
*/
// ==================== Message 相关 ====================
// 类型定义
export {
MessageUpdateType,
@@ -38,3 +40,45 @@ export {
BatcherOptions,
DEFAULT_BATCHER_OPTIONS,
} from './MessageUpdateBatcher';
// ==================== Post 相关 ====================
// 类型定义
export {
PostChangeType,
PostUpdateType,
PostIdentifier,
PostChange,
PostDiffResult,
BasePostUpdate,
AddPostUpdate,
UpdatePostUpdate,
DeletePostUpdate,
MovePostUpdate,
BatchAddPostUpdate,
BatchUpdatePostUpdate,
BatchDeletePostUpdate,
ResetPostUpdate,
PostUpdate,
PostUpdateBatch,
PostDiffConfig,
PostBatchUpdateListener,
PostComparator,
PostSorter,
PostDiffStats,
DEFAULT_POST_DIFF_CONFIG,
} from './postTypes';
// 差异计算器
export {
PostDiffCalculator,
createPostDiffCalculator,
} from './PostDiffCalculator';
// 批量处理器
export {
PostUpdateBatcher,
createPostUpdateBatcher,
PostBatcherOptions,
DEFAULT_POST_BATCHER_OPTIONS,
} from './PostUpdateBatcher';

View File

@@ -0,0 +1,325 @@
/**
* Post差异更新类型定义
* 用于帖子列表的增量更新,减少 UI 重渲染次数
*/
import { Post } from '../../core/entities/Post';
// ==================== 变化类型枚举 ====================
/**
* 帖子变化类型枚举
*/
export enum PostChangeType {
/** 新增帖子 */
ADDED = 'added',
/** 更新帖子 */
UPDATED = 'updated',
/** 删除帖子 */
DELETED = 'deleted',
}
/**
* 帖子更新类型枚举
*/
export enum PostUpdateType {
/** 添加单条帖子 */
ADD = 'ADD',
/** 更新单条帖子 */
UPDATE = 'UPDATE',
/** 删除单条帖子 */
DELETE = 'DELETE',
/** 移动帖子位置 */
MOVE = 'MOVE',
/** 批量添加 */
BATCH_ADD = 'BATCH_ADD',
/** 批量更新 */
BATCH_UPDATE = 'BATCH_UPDATE',
/** 批量删除 */
BATCH_DELETE = 'BATCH_DELETE',
/** 重置整个列表 */
RESET = 'RESET',
}
// ==================== 基础接口 ====================
/**
* 帖子唯一标识接口
*/
export interface PostIdentifier {
/** 帖子唯一 ID */
id: string;
/** 帖子创建时间(可选,用于排序) */
createdAt?: string;
/** 更新时间(可选,用于检测变化) */
updatedAt?: string;
}
/**
* 单个帖子变化记录
*/
export interface PostChange<T extends PostIdentifier = Post> {
/** 变化类型 */
type: PostChangeType;
/** 帖子数据 */
post: T;
/** 变化前的索引位置(用于删除和移动) */
oldIndex?: number;
/** 变化后的索引位置(用于新增和移动) */
newIndex?: number;
/** 变化的字段(用于更新) */
changes?: Partial<T>;
}
// ==================== 差异结果接口 ====================
/**
* Post差异计算结果接口
*/
export interface PostDiffResult<T extends PostIdentifier = Post> {
/** 新增的帖子 */
added: T[];
/** 更新的帖子(包含变化详情) */
updated: Array<{
post: T;
index: number;
changes: Partial<T>;
}>;
/** 删除的帖子 */
deleted: Array<{
post: T;
index: number;
}>;
/** 移动的帖子 */
moved: Array<{
post: T;
fromIndex: number;
toIndex: number;
}>;
/** 未变更的帖子 */
unchanged: T[];
/** 是否需要重置(变化比例过大时) */
shouldReset: boolean;
}
// ==================== 更新操作接口 ====================
/**
* 基础帖子更新操作接口
*/
export interface BasePostUpdate {
/** 更新类型 */
type: PostUpdateType;
/** 更新时间戳 */
timestamp: number;
/** 社区 ID可选 */
communityId?: string;
/** 载荷数据(可选,用于合并更新) */
payload?: Record<string, any>;
}
/**
* 添加单条帖子更新
*/
export interface AddPostUpdate extends BasePostUpdate {
type: PostUpdateType.ADD;
/** 帖子数据 */
post: PostIdentifier;
/** 插入位置索引(可选,默认追加到末尾) */
index?: number;
}
/**
* 更新单条帖子更新
*/
export interface UpdatePostUpdate extends BasePostUpdate {
type: PostUpdateType.UPDATE;
/** 帖子 ID */
postId: string;
/** 更新的字段 */
updates: Partial<PostIdentifier>;
}
/**
* 删除单条帖子更新
*/
export interface DeletePostUpdate extends BasePostUpdate {
type: PostUpdateType.DELETE;
/** 帖子 ID */
postId: string;
}
/**
* 移动帖子更新
*/
export interface MovePostUpdate extends BasePostUpdate {
type: PostUpdateType.MOVE;
/** 帖子 ID */
postId: string;
/** 目标位置索引 */
toIndex: number;
}
/**
* 批量添加帖子更新
*/
export interface BatchAddPostUpdate extends BasePostUpdate {
type: PostUpdateType.BATCH_ADD;
/** 帖子列表 */
posts: PostIdentifier[];
/** 插入位置索引(可选,默认追加到末尾) */
startIndex?: number;
}
/**
* 批量更新帖子更新
*/
export interface BatchUpdatePostUpdate extends BasePostUpdate {
type: PostUpdateType.BATCH_UPDATE;
/** 批量更新项 */
updates: Array<{
postId: string;
changes: Partial<PostIdentifier>;
}>;
}
/**
* 批量删除帖子更新
*/
export interface BatchDeletePostUpdate extends BasePostUpdate {
type: PostUpdateType.BATCH_DELETE;
/** 要删除的帖子 ID 列表 */
postIds: string[];
}
/**
* 重置帖子列表更新
*/
export interface ResetPostUpdate extends BasePostUpdate {
type: PostUpdateType.RESET;
/** 新的帖子列表 */
posts: PostIdentifier[];
}
/**
* 帖子更新联合类型
*/
export type PostUpdate =
| AddPostUpdate
| UpdatePostUpdate
| DeletePostUpdate
| MovePostUpdate
| BatchAddPostUpdate
| BatchUpdatePostUpdate
| BatchDeletePostUpdate
| ResetPostUpdate;
// ==================== 批量更新接口 ====================
/**
* Post批量更新类型
*/
export interface PostUpdateBatch {
/** 批次 ID */
batchId: string;
/** 更新列表 */
updates: PostUpdate[];
/** 创建时间戳 */
createdAt: number;
/** 是否已处理 */
processed: boolean;
}
// ==================== 配置接口 ====================
/**
* Post差异配置接口
*/
export interface PostDiffConfig {
/** 批量处理间隔(毫秒),默认 16ms60fps */
batchInterval?: number;
/** 最大批量大小,默认 50 */
maxBatchSize?: number;
/** 是否启用去重,默认 true */
enableDeduplication?: boolean;
/** 是否合并相同帖子的多次更新,默认 true */
enableMerge?: boolean;
/** 帖子唯一标识字段,默认 'id' */
idField?: string;
/** 排序字段,默认 'createdAt' */
sortField?: string;
/** 变化比例阈值,超过此值触发重置,默认 0.5 */
resetThreshold?: number;
/** 是否检测部分字段更新 */
detectPartialUpdates?: boolean;
/** 需要检测变化的字段列表 */
trackedFields?: (keyof Post)[];
}
/**
* 默认Post差异配置常量
*/
export const DEFAULT_POST_DIFF_CONFIG: Required<PostDiffConfig> = {
batchInterval: 16,
maxBatchSize: 50,
enableDeduplication: true,
enableMerge: true,
idField: 'id',
sortField: 'createdAt',
resetThreshold: 0.5,
detectPartialUpdates: true,
trackedFields: [
'likesCount',
'commentsCount',
'sharesCount',
'viewsCount',
'favoritesCount',
'isLiked',
'isFavorited',
'isPinned',
'status',
'title',
'content',
'images',
'tags',
],
};
// ==================== 监听器类型 ====================
/**
* 批量更新监听器类型
*/
export type PostBatchUpdateListener = (updates: PostUpdate[]) => void;
/**
* Post比较函数类型
*/
export type PostComparator<T extends PostIdentifier> = (a: T, b: T) => boolean;
/**
* Post排序函数类型
*/
export type PostSorter<T extends PostIdentifier> = (a: T, b: T) => number;
// ==================== 统计接口 ====================
/**
* Post差异统计接口
*/
export interface PostDiffStats {
/** 总批次数 */
totalBatches: number;
/** 总更新数 */
totalUpdates: number;
/** 平均批量大小 */
averageBatchSize: number;
/** 上次批次时间 */
lastBatchTime: number;
/** 新增帖子数 */
addedCount: number;
/** 更新帖子数 */
updatedCount: number;
/** 删除帖子数 */
deletedCount: number;
}