refactor(core): introduce EventBus and refactor store infrastructure
- Add EventBus for decoupled event-driven communication between services - Add EventSubscriber component for centralized event handling - Add requestDedupe utility for shared request deduplication - Refactor api/wsService to use eventBus instead of direct router navigation - Extract MessageMapper.toCachedMessages for consistent message mapping - Remove deprecated BaseManager and CacheBus classes - Improve @mention rendering with memberMap support in segment rendering - Update hooks to use useRef instead of useState for fetch tracking
This commit is contained in:
@@ -8,6 +8,7 @@ import { groupService } from '../../services/groupService';
|
||||
import { groupCacheRepository, userCacheRepository } from '@/database';
|
||||
import { useGroupManagerStore } from './groupStore';
|
||||
import { createCacheEntry, isCacheExpired, DEFAULT_TTL } from '../utils/cache';
|
||||
import { createDedupe } from '../utils/requestDedupe';
|
||||
import {
|
||||
IGroupListPagedSource,
|
||||
IGroupMemberListPagedSource,
|
||||
@@ -18,6 +19,8 @@ import {
|
||||
GROUP_MEMBER_LIST_PAGE_SIZE,
|
||||
} from '../groupListSources';
|
||||
|
||||
const dedupe = createDedupe(() => useGroupManagerStore);
|
||||
|
||||
function buildMembersKey(groupId: string, page: number, pageSize: number): string {
|
||||
return `members:${groupId}:${page}:${pageSize}`;
|
||||
}
|
||||
@@ -74,7 +77,7 @@ class GroupManager {
|
||||
}
|
||||
}
|
||||
|
||||
return this.dedupe(`groups:list:${page}:${pageSize}`, async () => {
|
||||
return dedupe(`groups:list:${page}:${pageSize}`, async () => {
|
||||
const store = useGroupManagerStore.getState();
|
||||
const response = await groupService.getGroups(page, pageSize);
|
||||
const groups = response.list || [];
|
||||
@@ -87,7 +90,7 @@ class GroupManager {
|
||||
}
|
||||
|
||||
private refreshGroupsInBackground(page = 1, pageSize = GROUP_LIST_PAGE_SIZE): void {
|
||||
this.dedupe(`groups:bg:list:${page}:${pageSize}`, async () => {
|
||||
dedupe(`groups:bg:list:${page}:${pageSize}`, async () => {
|
||||
const store = useGroupManagerStore.getState();
|
||||
const response = await groupService.getGroups(page, pageSize);
|
||||
const groups = response.list || [];
|
||||
@@ -130,7 +133,7 @@ class GroupManager {
|
||||
}
|
||||
}
|
||||
|
||||
return this.dedupe(`groups:detail:${groupId}`, async () => {
|
||||
return dedupe(`groups:detail:${groupId}`, async () => {
|
||||
const store = useGroupManagerStore.getState();
|
||||
const group = await groupService.getGroup(groupId);
|
||||
store.setGroup(groupId, group);
|
||||
@@ -140,7 +143,7 @@ class GroupManager {
|
||||
}
|
||||
|
||||
private refreshGroupInBackground(groupId: string): void {
|
||||
this.dedupe(`groups:detail:bg:${groupId}`, async () => {
|
||||
dedupe(`groups:detail:bg:${groupId}`, async () => {
|
||||
const store = useGroupManagerStore.getState();
|
||||
const group = await groupService.getGroup(groupId);
|
||||
store.setGroup(groupId, group);
|
||||
@@ -179,7 +182,7 @@ class GroupManager {
|
||||
}
|
||||
}
|
||||
|
||||
return this.dedupe(`groups:members:${key}`, async () => {
|
||||
return dedupe(`groups:members:${key}`, async () => {
|
||||
const store = useGroupManagerStore.getState();
|
||||
const response = await groupService.getMembers(groupId, page, pageSize);
|
||||
const members = response.list || [];
|
||||
@@ -202,7 +205,7 @@ class GroupManager {
|
||||
pageSize = GROUP_MEMBER_LIST_PAGE_SIZE
|
||||
): void {
|
||||
const key = buildMembersKey(groupId, page, pageSize);
|
||||
this.dedupe(`groups:members:bg:${key}`, async () => {
|
||||
dedupe(`groups:members:bg:${key}`, async () => {
|
||||
const store = useGroupManagerStore.getState();
|
||||
const response = await groupService.getMembers(groupId, page, pageSize);
|
||||
const members = response.list || [];
|
||||
@@ -240,18 +243,6 @@ class GroupManager {
|
||||
clear(): void {
|
||||
useGroupManagerStore.getState().reset();
|
||||
}
|
||||
|
||||
private async dedupe<T>(key: string, fetcher: () => Promise<T>): Promise<T> {
|
||||
const store = useGroupManagerStore.getState();
|
||||
const pending = store.getPendingRequest<T>(key);
|
||||
if (pending) return pending;
|
||||
|
||||
const request = fetcher().finally(() => {
|
||||
useGroupManagerStore.getState().deletePendingRequest(key);
|
||||
});
|
||||
store.setPendingRequest(key, request);
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
export const groupManager = new GroupManager();
|
||||
|
||||
@@ -1,38 +1,29 @@
|
||||
/**
|
||||
* 群组相关 React Hooks
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { useGroupManagerStore } from './groupStore';
|
||||
import { groupManager } from './GroupManager';
|
||||
import type { GroupResponse, GroupMemberResponse } from '../../types/dto';
|
||||
import { GROUP_LIST_PAGE_SIZE, GROUP_MEMBER_LIST_PAGE_SIZE } from '../groupListSources';
|
||||
|
||||
// ==================== useGroups ====================
|
||||
|
||||
export interface UseGroupsResult {
|
||||
groups: GroupResponse[];
|
||||
isLoading: boolean;
|
||||
refresh: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群组列表 hook
|
||||
*/
|
||||
export function useGroups(): UseGroupsResult {
|
||||
const groups = useGroupManagerStore(state => state.groupsListEntry?.data ?? []);
|
||||
const isLoading = useGroupManagerStore(state => state.isLoadingGroups);
|
||||
const [hasFetched, setHasFetched] = useState(false);
|
||||
const hasFetchedRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasFetched) {
|
||||
setHasFetched(true);
|
||||
if (!hasFetchedRef.current) {
|
||||
hasFetchedRef.current = true;
|
||||
useGroupManagerStore.getState().setLoadingGroups(true);
|
||||
groupManager.getGroups(1, GROUP_LIST_PAGE_SIZE).finally(() => {
|
||||
useGroupManagerStore.getState().setLoadingGroups(false);
|
||||
});
|
||||
}
|
||||
}, [hasFetched]);
|
||||
}, []);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
useGroupManagerStore.getState().setLoadingGroups(true);
|
||||
@@ -46,17 +37,12 @@ export function useGroups(): UseGroupsResult {
|
||||
return { groups, isLoading, refresh };
|
||||
}
|
||||
|
||||
// ==================== useGroup ====================
|
||||
|
||||
export interface UseGroupResult {
|
||||
group: GroupResponse | null;
|
||||
isLoading: boolean;
|
||||
refresh: () => Promise<GroupResponse | null>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个群组 hook
|
||||
*/
|
||||
export function useGroup(groupId: string | undefined | null): UseGroupResult {
|
||||
const group = useGroupManagerStore(state =>
|
||||
groupId ? (state.groupsMap.get(groupId)?.data ?? null) : null
|
||||
@@ -64,18 +50,18 @@ export function useGroup(groupId: string | undefined | null): UseGroupResult {
|
||||
const isLoading = useGroupManagerStore(state =>
|
||||
groupId ? state.loadingGroupIds.has(groupId) : false
|
||||
);
|
||||
const [hasFetched, setHasFetched] = useState(false);
|
||||
const lastFetchedRef = useRef<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!groupId) return;
|
||||
if (!hasFetched) {
|
||||
setHasFetched(true);
|
||||
if (lastFetchedRef.current !== groupId) {
|
||||
lastFetchedRef.current = groupId;
|
||||
useGroupManagerStore.getState().setLoadingGroup(groupId, true);
|
||||
groupManager.getGroup(groupId).finally(() => {
|
||||
useGroupManagerStore.getState().setLoadingGroup(groupId, false);
|
||||
});
|
||||
}
|
||||
}, [groupId, hasFetched]);
|
||||
}, [groupId]);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
if (!groupId) return null;
|
||||
@@ -90,17 +76,12 @@ export function useGroup(groupId: string | undefined | null): UseGroupResult {
|
||||
return { group, isLoading, refresh };
|
||||
}
|
||||
|
||||
// ==================== useGroupMembers ====================
|
||||
|
||||
export interface UseGroupMembersResult {
|
||||
members: GroupMemberResponse[];
|
||||
isLoading: boolean;
|
||||
refresh: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群组成员 hook
|
||||
*/
|
||||
export function useGroupMembers(
|
||||
groupId: string | undefined | null,
|
||||
page = 1,
|
||||
@@ -113,18 +94,19 @@ export function useGroupMembers(
|
||||
const isLoading = useGroupManagerStore(state =>
|
||||
membersKey ? state.loadingMemberKeys.has(membersKey) : false
|
||||
);
|
||||
const [hasFetched, setHasFetched] = useState(false);
|
||||
const lastFetchedRef = useRef<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!groupId) return;
|
||||
if (!hasFetched) {
|
||||
setHasFetched(true);
|
||||
const key = `${groupId}:${page}:${pageSize}`;
|
||||
if (lastFetchedRef.current !== key) {
|
||||
lastFetchedRef.current = key;
|
||||
useGroupManagerStore.getState().setLoadingMembers(groupId, true, page, pageSize);
|
||||
groupManager.getMembers(groupId, page, pageSize).finally(() => {
|
||||
useGroupManagerStore.getState().setLoadingMembers(groupId, false, page, pageSize);
|
||||
});
|
||||
}
|
||||
}, [groupId, page, pageSize, hasFetched]);
|
||||
}, [groupId, page, pageSize]);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
if (!groupId) return;
|
||||
@@ -137,4 +119,4 @@ export function useGroupMembers(
|
||||
}, [groupId, page, pageSize]);
|
||||
|
||||
return { members, isLoading, refresh };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user