# 第三章:状态管理架构设计 - 从 Zustand 到 React Query 的完整实践

在上一章中,我们深入探讨了 Expo Router 的路由架构设计。本章将详细介绍维小美医疗云的状态管理架构,包括 Zustand 全局状态管理、React Query 服务端状态管理、以及两者如何协同工作构建高效的数据流。

3.1 状态管理架构概览

3.1.1 状态分类与职责划分

在医疗应用中,我们需要管理多种类型的状态。我们采用了清晰的状态分类策略:

typescript 复制代码
// 状态分类架构
状态管理架构
├── 客户端状态 (Zustand)
│   ├── 全局应用状态 (AppState)
│   ├── 用户认证状态 (UserState)
│   ├── UI交互状态 (UIState)
│   └── 业务状态 (BusinessState)
└── 服务端状态 (React Query)
    ├── 患者数据 (Patient Data)
    ├── 预约数据 (Appointment Data)
    ├── 病历数据 (Medical Records)
    └── 统计数据 (Statistics Data)

3.1.2 状态管理原则

我们遵循以下核心原则来设计状态管理架构:

  1. 单一数据源:每种状态只有一个权威来源
  2. 状态不可变性:状态更新必须通过纯函数进行
  3. 状态持久化:关键状态需要持久化到本地存储
  4. 类型安全:所有状态都有完整的 TypeScript 类型定义
  5. 性能优化:合理使用缓存和订阅优化

3.2 Zustand 全局状态管理实践

3.2.1 应用状态管理 (AppState)

应用状态管理全局的应用配置和生命周期状态:

typescript 复制代码
// stores/global/appStore.ts
export interface AppState {
  // 应用设置
  theme: 'light' | 'dark';
  language: 'zh-CN' | 'en-US';
  version: string;

  // 网络状态
  isOnline: boolean;

  // 应用状态
  isLoading: boolean;
  error: string | null;

  // 启动/登录相关
  isBooted: boolean; // App 是否已初始化
  splashLoading: boolean; // 启动页加载中
  hasTriedAutoLogin: boolean; // 是否尝试过自动登录
  lastLoginTime: number | null; // 上次登录时间戳
  lastUserId: string | null; // 上次登录用户ID

  // PWA 相关
  isInstalled: boolean;
  updateAvailable: boolean;

  // Actions
  setTheme: (theme: 'light' | 'dark') => void;
  setLanguage: (language: 'zh-CN' | 'en-US') => void;
  setOnlineStatus: (isOnline: boolean) => void;
  setLoading: (isLoading: boolean) => void;
  setError: (error: string | null) => void;
  setInstalled: (isInstalled: boolean) => void;
  setUpdateAvailable: (updateAvailable: boolean) => void;
  clearError: () => void;
  reset: () => void;

  // 启动/登录相关 actions
  setBooted: (isBooted: boolean) => void;
  setSplashLoading: (loading: boolean) => void;
  setHasTriedAutoLogin: (tried: boolean) => void;
  setLastLoginTime: (time: number | null) => void;
  setLastUserId: (userId: string | null) => void;
}

const initialState = {
  theme: 'light' as const,
  language: 'zh-CN' as const,
  version: '1.0.0',
  isOnline: true,
  isLoading: false,
  error: null,
  isInstalled: false,
  updateAvailable: false,
  isBooted: false,
  splashLoading: true,
  hasTriedAutoLogin: false,
  lastLoginTime: null,
  lastUserId: null,
};

export const useAppStore = create<AppState>()(
  persist(
    (set, get) => ({
      ...initialState,

      setTheme: (theme) => set({ theme }),
      setLanguage: (language) => set({ language }),
      setOnlineStatus: (isOnline) => set({ isOnline }),
      setLoading: (isLoading) => set({ isLoading }),
      setError: (error) => set({ error }),
      setInstalled: (isInstalled) => set({ isInstalled }),
      setUpdateAvailable: (updateAvailable) => set({ updateAvailable }),
      clearError: () => set({ error: null }),
      reset: () => set(initialState),
      setBooted: (isBooted) => set({ isBooted }),
      setSplashLoading: (loading) => set({ splashLoading: loading }),
      setHasTriedAutoLogin: (tried) => set({ hasTriedAutoLogin: tried }),
      setLastLoginTime: (time) => set({ lastLoginTime: time }),
      setLastUserId: (userId) => set({ lastUserId: userId }),
    }),
    {
      name: 'app-storage',
      storage: createJSONStorage(() => AsyncStorage),
      partialize: (state) => ({
        // 只持久化必要的状态
        theme: state.theme,
        language: state.language,
        isInstalled: state.isInstalled,
        isBooted: state.isBooted,
        splashLoading: state.splashLoading,
        hasTriedAutoLogin: state.hasTriedAutoLogin,
        lastLoginTime: state.lastLoginTime,
        lastUserId: state.lastUserId,
      }),
    }
  )
);

3.2.2 用户认证状态管理 (UserState)

用户认证状态管理用户的登录状态和权限信息:

typescript 复制代码
// stores/auth/userStore.ts
export interface User {
  id: string;
  username: string;
  email?: string;
  phone?: string;
  avatar?: string;
  nickname?: string;
  department?: string;
  role?: string;
  permissions?: string[];
  affiliated_org: number;
  org_type_code?: string;
}

export interface UserState {
  // 用户信息
  user: User | null;
  isAuthenticated: boolean;
  token: string | null;
  refreshToken: string | null;

  // 登录状态
  isLoggingIn: boolean;
  loginError: string | null;

  // Actions
  setUser: (user: User) => void;
  setTokens: (token: string, refreshToken?: string) => void;
  login: (username: string, password: string) => Promise<DoLoginUsingPostResponse>;
  logout: () => void;
  updateUser: (updates: Partial<User>) => void;
  clearError: () => void;
  setLoggingIn: (isLoggingIn: boolean) => void;
  restoreUserFromStorage: () => Promise<void>;
}

export const useUserStore = create<UserState>()(
  persist(
    (set, get) => ({
      user: null,
      isAuthenticated: false,
      token: null,
      refreshToken: null,
      isLoggingIn: false,
      loginError: null,

      setUser: (user) => {
        set({ user, isAuthenticated: !!user });
        // 同时保存到独立存储
        if (user) {
          setUserInStorage(user as unknown as UserState);
        }
      },

      setTokens: (token, refreshToken) => set({
        token,
        refreshToken,
        isAuthenticated: !!token
      }),

      login: async (username, password) => {
        set({ isLoggingIn: true, loginError: null });
        try {
          const res: DoLoginUsingPostResponse = await doLoginUsingPost({
            body: { username, password }
          });
          if (res.status_code === 0 && res.data) {
            console.log('✅ userStore.login: 登录成功,开始保存 token...', {
              hasAccessToken: !!res.data.access_token,
              hasRefreshToken: !!res.data.refresh_token,
              userId: res.data.userid
            });

            await setAccessToken(res.data.access_token);
            await setRefreshToken(res.data.refresh_token);
            console.log('✅ userStore.login: token 保存到安全存储完成');

            const userPayload: User = {
              id: res.data.userid,
              username: res.data.username,
              nickname: res.data.username,
              affiliated_org: res.data.affiliated_org as number,
              org_type_code: res.data.org_type_code,
              department: '',
              role: '',
              permissions: ['image:view', 'image:medical'], // 实际应该从后端获取
            };

            await setUserInStorage(userPayload as unknown as UserState);
            console.log('✅ userStore.login: 用户信息保存到通用存储完成');

            set({
              token: res.data.access_token,
              refreshToken: res.data.refresh_token,
              user: userPayload,
              isAuthenticated: true,
              isLoggingIn: false,
              loginError: null
            });
            console.log('✅ userStore.login: 用户状态设置完成');
            return res;
          } else {
            throw res.status_msg || '登录失败';
          }
        } catch (error: any) {
          set({
            loginError: error,
            isLoggingIn: false,
            isAuthenticated: false
          });
          throw error;
        }
      },

      logout: async () => {
        console.log('🚪 userStore.logout: 开始登出流程...');
        const res = await doLogoutUsingGet();
        if( !(res?.status_code === 0) ) {
          Alert.alert('登出失败', '登出失败,请联系管理员');
          console.log('🚪 userStore.logout: 登出失败');
          return
        }

        // 清除安全存储
        console.log('🗑️ userStore.logout: 清除安全存储...');
        await removeAccessToken();
        await removeRefreshToken();
        await removeUser();

        // 清除 store 状态
        console.log('🗑️ userStore.logout: 清除用户状态...');
        set({
          user: null,
          token: null,
          refreshToken: null,
          isAuthenticated: false,
          loginError: null
        });

        console.log('✅ userStore.logout: 登出完成');
      },

      updateUser: (updates) => {
        const currentUser = get().user;
        if (currentUser) {
          set({ user: { ...currentUser, ...updates } });
        }
      },

      clearError: () => set({ loginError: null }),

      setLoggingIn: (isLoggingIn) => set({ isLoggingIn }),

      // 从存储中恢复用户信息
      restoreUserFromStorage: async () => {
        try {
          console.log('🔄 开始从存储恢复用户信息...');
          const storedUser = await getUser();
          if (storedUser && storedUser.user) {
            console.log('✅ 成功恢复用户信息:', storedUser.user.username);
            set({
              user: storedUser.user as User,
              isAuthenticated: true
            });
          } else {
            console.warn('⚠️ 存储中没有找到用户信息');
          }
        } catch (error) {
          console.error('❌ 恢复用户信息失败:', error);
        }
      },
    }),
    {
      name: 'user-storage',
      storage: createJSONStorage(() => AsyncStorage),
      partialize: (state) => ({
        // 持久化认证状态和用户信息,token由SecureStore管理
        isAuthenticated: state.isAuthenticated,
        user: state.user,
      }),
    }
  )
);

3.2.3 UI 状态管理 (UIState)

UI 状态管理全局的界面交互状态:

typescript 复制代码
// stores/global/uiStore.ts
export interface UIState {
  // 模态框状态,控制各类弹窗的显示与隐藏
  modals: {
    departmentSelector: boolean; // 部门选择弹窗
    errorModal: boolean;         // 错误提示弹窗
    wechatModal: boolean;        // 微信相关弹窗
    confirmModal: boolean;       // 通用确认弹窗
  };

  // 加载状态,控制全局及各功能的 loading
  loading: {
    global: boolean; // 全局 loading
    search: boolean; // 搜索 loading
    login: boolean;  // 登录 loading
    stats: boolean;  // 统计 loading
  };

  // 搜索相关状态
  searchValue: string;      // 当前搜索框内容
  searchHistory: string[];  // 搜索历史记录

  // 选择器状态
  selectedDepartment: string; // 当前选中的部门

  // 通知状态,存储所有通知
  notifications: Notification[];

  // 页面状态,可存储任意页面相关的临时状态
  pageStates: {
    [key: string]: any;
  };

  // ========== Actions ========== //
  // 打开指定弹窗
  openModal: (modalName: keyof UIState['modals']) => void;
  // 关闭指定弹窗
  closeModal: (modalName: keyof UIState['modals']) => void;
  // 关闭所有弹窗
  closeAllModals: () => void;

  // 设置 loading 状态
  setLoading: (key: keyof UIState['loading'], value: boolean) => void;
  // 设置全局 loading
  setGlobalLoading: (loading: boolean) => void;

  // 设置搜索框内容
  setSearchValue: (value: string) => void;
  // 添加搜索历史
  addSearchHistory: (value: string) => void;
  // 清空搜索历史
  clearSearchHistory: () => void;

  // 设置当前选中部门
  setSelectedDepartment: (department: string) => void;

  // 添加通知
  addNotification: (notification: Omit<Notification, 'id' | 'timestamp'>) => void;
  // 移除通知
  removeNotification: (id: string) => void;
  // 清空所有通知
  clearNotifications: () => void;

  // 设置页面状态
  setPageState: (page: string, state: any) => void;
  // 获取页面状态
  getPageState: (page: string) => any;
  // 清除页面状态
  clearPageState: (page: string) => void;
}

// UI 状态 Store 实现
export const useUIStore = create<UIState>((set, get) => ({
  ...initialState,

  // 打开指定弹窗
  openModal: (modalName) =>
    set((state) => ({
      modals: { ...state.modals, [modalName]: true }
    })),

  // 关闭指定弹窗
  closeModal: (modalName) =>
    set((state) => ({
      modals: { ...state.modals, [modalName]: false }
    })),

  // 关闭所有弹窗
  closeAllModals: () =>
    set((state) => ({
      modals: Object.keys(state.modals).reduce((acc, key) => {
        acc[key as keyof typeof state.modals] = false;
        return acc;
      }, {} as typeof state.modals)
    })),

  // 设置 loading 状态
  setLoading: (key, value) =>
    set((state) => ({
      loading: { ...state.loading, [key]: value }
    })),

  // 设置全局 loading
  setGlobalLoading: (loading) =>
    set((state) => ({
      loading: { ...state.loading, global: loading }
    })),

  // 设置搜索框内容
  setSearchValue: (value) => set({ searchValue: value }),

  // 添加搜索历史(去重,最多10条)
  addSearchHistory: (value) => {
    const { searchHistory } = get();
    const newHistory = [value, ...searchHistory.filter(item => item !== value)].slice(0, 10);
    set({ searchHistory: newHistory });
  },

  // 清空搜索历史
  clearSearchHistory: () => set({ searchHistory: [] }),

  // 设置当前选中部门
  setSelectedDepartment: (department) => set({ selectedDepartment: department }),

  // 添加通知(自动生成唯一 id,支持自动移除)
  addNotification: (notification) => {
    const id = generateNotificationId();
    const newNotification: Notification = {
      ...notification,
      id,
      timestamp: dayjs().toISOString(),
    };

    set((state) => ({
      notifications: [...state.notifications, newNotification]
    }));

    // 自动移除通知(可选)
    if (notification.autoRemove !== false) {
      setTimeout(() => {
        get().removeNotification(id);
      }, notification.duration || 5000);
    }
  },

  // 移除通知
  removeNotification: (id) =>
    set((state) => ({
      notifications: state.notifications.filter(n => n.id !== id)
    })),

  // 清空所有通知
  clearNotifications: () => set({ notifications: [] }),

  // 设置页面状态
  setPageState: (page, state) =>
    set((currentState) => ({
      pageStates: { ...currentState.pageStates, [page]: state }
    })),

  // 获取页面状态
  getPageState: (page) => {
    const { pageStates } = get();
    return pageStates[page];
  },

  // 清除页面状态
  clearPageState: (page) =>
    set((state) => {
      const newPageStates = { ...state.pageStates };
      delete newPageStates[page];
      return { pageStates: newPageStates };
    }),
}));

3.2.4 业务状态管理

患者选择状态管理

在医疗应用中,患者选择是一个复杂的跨页面流程:

typescript 复制代码
// stores/patientSelectionStore.ts
export interface SelectedPatient {
  id: string;
  name: string;
  gender: string;
  age: string | number;
  phone: string;
  recordId?: string;
  consultItem?: string;
  source?: string;
}

export type SourcePageType = 'add-appointment' | 'edit-appointment' | 'patient-detail' | null;

interface PatientSelectionState {
  // 选中的患者信息
  selectedPatient: SelectedPatient | null;

  // 来源页面标识
  sourcePageType: SourcePageType;

  // 保存的表单数据(用于新建预约页面)
  savedFormData: Record<string, any> | null;

  // 编辑预约的ID(用于编辑页面)
  editReservationId: string | null;

  // 返回路径(用于从患者搜索返回)
  returnPath: string | null;

  // 是否正在处理患者选择(防止重复处理)
  isProcessingPatientSelection: boolean;

  // 最后处理的患者ID(用于检测患者变化)
  lastProcessedPatientId: string | null;

  // Actions
  setSelectedPatient: (patient: SelectedPatient | null) => void;
  setSourcePage: (pageType: SourcePageType) => void;
  setSavedFormData: (formData: Record<string, any> | null) => void;
  setEditReservationId: (id: string | null) => void;
  setReturnPath: (path: string | null) => void;
  setProcessingState: (isProcessing: boolean) => void;
  setLastProcessedPatientId: (id: string | null) => void;

  // 清除所有状态
  clearAll: () => void;

  // 获取并清除患者信息(一次性使用)
  consumeSelectedPatient: () => SelectedPatient | null;

  // 设置患者信息并保存表单数据(用于患者搜索场景)
  setPatientWithFormData: (
    patient: SelectedPatient,
    formData?: Record<string, any>,
    sourceType?: SourcePageType
  ) => void;

  // 准备患者搜索(保存当前状态)
  preparePatientSearch: (
    formData: Record<string, any>,
    sourceType: SourcePageType,
    editId?: string
  ) => void;

  shouldProcessPatient: (patientId: string) => boolean;
}

export const usePatientSelectionStore = create<PatientSelectionState>((set, get) => ({
  selectedPatient: null,
  sourcePageType: null,
  savedFormData: null,
  editReservationId: null,
  returnPath: null,
  isProcessingPatientSelection: false,
  lastProcessedPatientId: null,

  setSelectedPatient: (patient) => set({ selectedPatient: patient }),
  setSourcePage: (pageType) => set({ sourcePageType: pageType }),
  setSavedFormData: (formData) => set({ savedFormData: formData }),
  setEditReservationId: (id) => set({ editReservationId: id }),
  setReturnPath: (path) => set({ returnPath: path }),
  setProcessingState: (isProcessing) => set({ isProcessingPatientSelection: isProcessing }),
  setLastProcessedPatientId: (id) => set({ lastProcessedPatientId: id }),

  clearAll: () => set({
    selectedPatient: null,
    sourcePageType: null,
    savedFormData: null,
    editReservationId: null,
    returnPath: null,
    isProcessingPatientSelection: false,
    lastProcessedPatientId: null,
  }),

  consumeSelectedPatient: () => {
    const { selectedPatient } = get();
    // 获取后立即清除,确保只使用一次
    set({
      selectedPatient: null,
      isProcessingPatientSelection: false,
      lastProcessedPatientId: null
    });
    return selectedPatient;
  },

  setPatientWithFormData: (patient, formData, sourceType) => set({
    selectedPatient: patient,
    savedFormData: formData || null,
    sourcePageType: sourceType || null,
    isProcessingPatientSelection: false,
    lastProcessedPatientId: patient.id,
  }),

  preparePatientSearch: (formData, sourceType, editId) => set({
    savedFormData: formData,
    sourcePageType: sourceType,
    editReservationId: editId || null,
    isProcessingPatientSelection: false,
  }),

  shouldProcessPatient: (patientId) => {
    const { isProcessingPatientSelection, lastProcessedPatientId } = get();
    return !isProcessingPatientSelection && lastProcessedPatientId !== patientId;
  },
}));
工作统计状态管理
typescript 复制代码
// stores/business/workStatsStore.ts
export interface WorkStatistic {
  indicator: StatisticsIndicator;
  dimension: StatisticsDimension;
  value: number;
  unit?: string;
  lastUpdated: string;
}

export interface WorkStatsState {
  statistics: WorkStatistic[];
  isLoading: boolean;
  error: string | null;
  lastFetchTime: string | null;

  setStatistics: (stats: WorkStatistic[]) => void;
  addStatistic: (stat: WorkStatistic) => void;
  updateStatistic: (indicator: StatisticsIndicator, dimension: StatisticsDimension, value: number) => void;
  setLoading: (loading: boolean) => void;
  setError: (error: string | null) => void;
  clearError: () => void;
  getStatistic: (indicator: StatisticsIndicator, dimension: StatisticsDimension) => WorkStatistic | undefined;
  clearStatistics: () => void;
}

// 创建工作统计 store
export const useWorkStatsStore = create<WorkStatsState>((set, get) => ({
  statistics: [],
  isLoading: false,
  error: null,
  lastFetchTime: null,

  setStatistics: (stats) => set({
    statistics: stats,
    lastFetchTime: dayjs().toISOString(),
    error: null
  }),

  addStatistic: (stat) => set((state) => {
    const existingIndex = state.statistics.findIndex(
      s => s.indicator === stat.indicator && s.dimension === stat.dimension
    );

    let newStatistics;
    if (existingIndex >= 0) {
      // 更新现有统计
      newStatistics = [...state.statistics];
      newStatistics[existingIndex] = { ...stat, lastUpdated: dayjs().toISOString() };
    } else {
      // 添加新统计
      newStatistics = [...state.statistics, { ...stat, lastUpdated: dayjs().toISOString() }];
    }

    return {
      statistics: newStatistics,
      lastFetchTime: dayjs().toISOString(),
      error: null
    };
  }),

  updateStatistic: (indicator, dimension, value) => set((state) => {
    const newStatistics = state.statistics.map(stat =>
      stat.indicator === indicator && stat.dimension === dimension
        ? { ...stat, value, lastUpdated: dayjs().toISOString() }
        : stat
    );

    return {
      statistics: newStatistics,
      lastFetchTime: dayjs().toISOString()
    };
  }),

  setLoading: (loading) => set({ isLoading: loading }),
  setError: (error) => set({ error, isLoading: false }),
  clearError: () => set({ error: null }),

  getStatistic: (indicator, dimension) => {
    const state = get();
    return state.statistics.find(
      stat => stat.indicator === indicator && stat.dimension === dimension
    );
  },

  clearStatistics: () => set({
    statistics: [],
    error: null,
    lastFetchTime: null
  }),
}));

3.2.5 状态持久化策略

我们使用 Zustand 的 persist 中间件来实现状态持久化:

typescript 复制代码
// 持久化配置示例
export const useAppStore = create<AppState>()(
  persist(
    (set, get) => ({
      // store 实现
    }),
    {
      name: 'app-storage', // 存储键名
      storage: createJSONStorage(() => AsyncStorage), // 使用 AsyncStorage
      partialize: (state) => ({
        // 只持久化必要的状态,避免存储敏感信息
        theme: state.theme,
        language: state.language,
        isInstalled: state.isInstalled,
        isBooted: state.isBooted,
        splashLoading: state.splashLoading,
        hasTriedAutoLogin: state.hasTriedAutoLogin,
        lastLoginTime: state.lastLoginTime,
        lastUserId: state.lastUserId,
      }),
    }
  )
);

3.2.6 状态订阅与优化

在组件中使用状态时,我们采用精确订阅来优化性能:

typescript 复制代码
// 精确订阅示例
const PatientListScreen = () => {
  // 只订阅需要的状态
  const { modals, openModal, closeModal } = useUIStore();
  const { user } = useUserStore();
  const { theme } = useAppStore();

  // 或者使用选择器函数
  const isGlobalLoading = useUIStore(state => state.loading.global);
  const selectedDepartment = useUIStore(state => state.selectedDepartment);

  return (
    // 组件实现
  );
};

3.3 React Query 服务端状态管理

3.3.1 查询键管理策略

我们建立了统一的查询键管理策略:

typescript 复制代码
// servers/case/caseService.ts
export const caseKeys = {
  all: ['case'] as const,
  lists: () => [...caseKeys.all, 'list'] as const,
  list: (customerId: number) => [...caseKeys.lists(), customerId] as const,
  details: () => [...caseKeys.all, 'detail'] as const,
  detail: (id: number) => [...caseKeys.details(), id] as const,
  templates: () => [...caseKeys.all, 'templates'] as const,
  template: (orgId: number) => [...caseKeys.templates(), orgId] as const,
};

// servers/billing/billingService.ts
export const billingKeys = {
  all: ['billing'] as const,
  lists: () => [...billingKeys.all, 'list'] as const,
  list: (customerId: number) => [...billingKeys.lists(), customerId] as const,
  listWithPage: (customerId: number, pageNum: number) =>
    [...billingKeys.list(customerId), pageNum] as const,
  details: () => [...billingKeys.all, 'detail'] as const,
  detail: (id: number) => [...billingKeys.details(), id] as const,
  summary: (customerId: number) => [...billingKeys.all, 'summary', customerId] as const,
};

3.3.2 基础查询实现

typescript 复制代码
// servers/case/caseReactQuery.ts
/**
 * 获取患者病历列表
 * @param customerId - 患者ID
 * @param loginOrgId - 登录机构ID
 * @returns 患者病历列表查询结果
 */
export function useCustomerEmr(customerId: number, loginOrgId: number) {
  return useQuery({
    queryKey: caseKeys.list(customerId),
    queryFn: async () => {
      const response = await getCustomerEmr({
        body: { customerId, loginOrgId },
      });
      return response;
    },
    enabled: !!customerId && customerId > 0 && !!loginOrgId && loginOrgId > 0,
    staleTime: 0, // 5分钟缓存
  });
}

/**
 * 获取病历模板树形结构
 * @param loginOrgId - 登录机构ID
 * @returns 模板树形结构查询结果
 */
export function useEmrItemTree(loginOrgId: number) {
  return useQuery({
    queryKey: caseKeys.template(loginOrgId),
    queryFn: async () => {
      const response = await getEmrItemTree({
        body: { loginOrgId },
      });
      return response;
    },
    enabled: !!loginOrgId && loginOrgId > 0,
    staleTime: 5 * 60 * 1000, // 5分钟缓存
  });
}

3.3.3 无限查询实现

对于分页数据,我们使用无限查询来优化用户体验:

typescript 复制代码
// servers/returnVisit/huifang.infinite.ts
/** 回访列表无限查询 - useInfiniteQuery 版本,用于分页加载 */
export function useFollowGetFollowListInfiniteQuery(
  params: {
    customerId: number;
    pageSize?: number;
  },
  options?: {
    enabled?: boolean;
    staleTime?: number;
  }
) {
  const pageSize = params.pageSize || 10;

  return useInfiniteQuery({
    queryKey: ['followUpListInfinite', params.customerId, pageSize],
    queryFn: async ({ pageParam = 0 }) => {
      console.log('🚀 执行回访列表无限查询:', {
        customerId: params.customerId,
        pageNum: pageParam,
        pageSize
      });

      const response = await apis.followGetFollowListUsingPost({
        body: {
          condition: {
            customerId: params.customerId,
          },
          pageNum: pageParam,
          pageSize: pageSize,
        },
      });

      console.log('📥 回访列表无限查询API响应:', response);

      if (response.status_code !== 0) {
        throw new Error(response.status_msg || '获取回访列表失败');
      }

      return response;
    },
    getNextPageParam: (lastPage) => {
      const { pageIndex = 0, pageCount = 1 } = lastPage.data || {};
      // 如果当前页小于总页数-1,返回下一页页码
      return pageIndex < pageCount - 1 ? pageIndex + 1 : undefined;
    },
    getPreviousPageParam: (firstPage) => {
      const { pageIndex = 0 } = firstPage.data || {};
      // 如果当前页大于0,返回上一页页码
      return pageIndex > 0 ? pageIndex - 1 : undefined;
    },
    enabled: options?.enabled !== false && !!params.customerId,
    staleTime: 0,
    gcTime: 10 * 60 * 1000,
    refetchOnWindowFocus: false,
    refetchOnMount: false,
    initialPageParam: 0,
  });
}

// servers/billing/billingInfinite.ts
/**
 * 无限查询 hook - 计费记录列表
 */
export function useBillingListInfinite(params: UseBillingListInfiniteParams) {
  const { customerId, pageSize = 20 } = params;

  return useInfiniteQuery({
    queryKey: [...billingKeys.list(customerId), 'infinite'],
    queryFn: async ({ pageParam = 0 }) => {
      const response = await getBillingList({
        body: {
          customerId,
          pageNum: pageParam,
          pageSize,
        },
      });
      return response;
    },
    getNextPageParam: (lastPage) => {
      const { pageIndex = 0, pageCount = 1 } = lastPage.data || {};
      return pageIndex < pageCount - 1 ? pageIndex + 1 : undefined;
    },
    initialPageParam: 0,
    enabled: !!customerId && customerId > 0,
    staleTime: 0, // 5分钟缓存
  });
}

/**
 * 获取扁平化的计费记录列表
 */
export function getFlatBillingList(data: any): BillingRecord[] {
  return data?.pages?.flatMap((page: any) => page.data?.resultList || []) || [];
}

3.3.4 变更操作实现

typescript 复制代码
// servers/case/caseReactQuery.ts
/**
 * 添加病历信息
 */
export function useAddCureEmrInfoMutation(options?: {
  onSuccess?: (data: AddCureEmrInfoResponse) => void;
  onError?: (error: any) => void;
}) {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: addCureEmrInfo,
    onSuccess: (data, variables) => {
      // 刷新相关查询
      queryClient.invalidateQueries({
        queryKey: caseKeys.list(variables.body.customerId),
        exact: false
      });

      showSuccessAlert('病历信息添加成功');
      options?.onSuccess?.(data);
    },
    onError: (error) => {
      const message = error?.response?.data?.status_msg
        || error?.message
        || '添加病历信息失败';
      Alert.alert('错误', message);
      options?.onError?.(error);
    },
  });
}

/**
 * 更新病历信息
 */
export function useUpdateCureEmrInfoMutation(options?: {
  onSuccess?: (data: UpdateCureEmrInfoResponse) => void;
  onError?: (error: any) => void;
}) {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: updateCureEmrInfo,
    onSuccess: (data, variables) => {
      // 刷新相关查询
      queryClient.invalidateQueries({
        queryKey: caseKeys.list(variables.body.customerId),
        exact: false
      });

      showSuccessAlert('病历信息更新成功');
      options?.onSuccess?.(data);
    },
    onError: (error) => {
      const message = error?.response?.data?.status_msg
        || error?.message
        || '更新病历信息失败';
      Alert.alert('错误', message);
      options?.onError?.(error);
    },
  });
}

这是第三章的第一部分,涵盖了 Zustand 状态管理和 React Query 的基础实现。接下来我将继续编写第二部分,包括缓存策略、错误处理、性能优化等内容。

第三章:状态管理架构设计 - 第二部分

3.4 缓存策略与数据同步

3.4.1 智能缓存管理

我们实现了全局的查询管理器来统一管理缓存策略:

typescript 复制代码
// utils/queryUtils.ts
export class QueryManager {
  private static instance: QueryManager;
  private queryClient: QueryClient | null = null;
  private appStateSubscription: any = null;

  private constructor() {}

  static getInstance(): QueryManager {
    if (!QueryManager.instance) {
      QueryManager.instance = new QueryManager();
    }
    return QueryManager.instance;
  }

  /**
   * 初始化 QueryClient
   */
  init(queryClient: QueryClient) {
    this.queryClient = queryClient;
    this.setupAppStateListener();
  }

  /**
   * 设置应用状态监听,当应用回到前台时刷新数据
   */
  private setupAppStateListener() {
    this.appStateSubscription = AppState.addEventListener('change', (nextAppState) => {
      if (nextAppState === 'active' && this.queryClient) {
        console.log('📱 应用回到前台,刷新数据...');
        this.queryClient.invalidateQueries();
      }
    });
  }

  /**
   * 清除所有缓存
   */
  clearAllCache() {
    if (!this.queryClient) return;
    console.log('🗑️ 清除所有缓存');
    this.queryClient.clear();
  }

  /**
   * 刷新所有查询
   */
  async refreshAllQueries() {
    if (!this.queryClient) return;
    console.log('🔄 刷新所有查询');
    await this.queryClient.invalidateQueries();
  }

  /**
   * 刷新特定的查询
   */
  async refreshQueries(queryKey: string[]) {
    if (!this.queryClient) return;
    console.log('🔄 刷新查询:', queryKey);
    await this.queryClient.invalidateQueries({ queryKey });
  }

  /**
   * 预取数据
   */
  async prefetchQuery(queryKey: string[], queryFn: () => Promise<any>) {
    if (!this.queryClient) return;
    console.log('📥 预取数据:', queryKey);
    await this.queryClient.prefetchQuery({
      queryKey,
      queryFn,
    });
  }

  /**
   * 获取缓存的查询数据
   */
  getQueryData(queryKey: string[]) {
    if (!this.queryClient) return null;
    return this.queryClient.getQueryData(queryKey);
  }

  /**
   * 设置查询数据到缓存
   */
  setQueryData(queryKey: string[], data: any) {
    if (!this.queryClient) return;
    this.queryClient.setQueryData(queryKey, data);
  }

  /**
   * 移除特定查询的缓存
   */
  removeQueries(queryKey: string[]) {
    if (!this.queryClient) return;
    console.log('🗑️ 移除查询缓存:', queryKey);
    this.queryClient.removeQueries({ queryKey });
  }

  /**
   * 获取查询状态
   */
  getQueryState(queryKey: string[]) {
    if (!this.queryClient) return null;
    return this.queryClient.getQueryState(queryKey);
  }

  /**
   * 销毁监听器
   */
  destroy() {
    if (this.appStateSubscription) {
      this.appStateSubscription.remove();
      this.appStateSubscription = null;
    }
  }
}

// 导出单例实例
export const queryManager = QueryManager.getInstance();

/**
 * Hook:在组件中使用全局查询管理器
 */
export function useQueryManager() {
  return {
    clearAllCache: () => queryManager.clearAllCache(),
    refreshAllQueries: () => queryManager.refreshAllQueries(),
    refreshQueries: (queryKey: string[]) => queryManager.refreshQueries(queryKey),
    prefetchQuery: (queryKey: string[], queryFn: () => Promise<any>) =>
      queryManager.prefetchQuery(queryKey, queryFn),
    getQueryData: (queryKey: string[]) => queryManager.getQueryData(queryKey),
    setQueryData: (queryKey: string[], data: any) => queryManager.setQueryData(queryKey, data),
    removeQueries: (queryKey: string[]) => queryManager.removeQueries(queryKey),
    getQueryState: (queryKey: string[]) => queryManager.getQueryState(queryKey),
  };
}

3.4.2 缓存失效策略

我们实现了精确的缓存失效策略,确保数据的一致性:

typescript 复制代码
// 在数据变更后刷新相关查询
export function useAddCureEmrInfoMutation(options?: {
  onSuccess?: (data: AddCureEmrInfoResponse) => void;
  onError?: (error: any) => void;
}) {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: addCureEmrInfo,
    onSuccess: (data, variables) => {
      // 刷新该患者的所有病历查询
      queryClient.invalidateQueries({
        queryKey: caseKeys.list(variables.body.customerId),
        exact: false // 不精确匹配,会刷新所有以此开头的查询键
      });

      // 同时刷新无限查询
      queryClient.invalidateQueries({
        queryKey: ['followUpListInfinite', variables.body.customerId],
        exact: false
      });

      showSuccessAlert('病历信息添加成功');
      options?.onSuccess?.(data);
    },
    onError: (error) => {
      const message = error?.response?.data?.status_msg
        || error?.message
        || '添加病历信息失败';
      Alert.alert('错误', message);
      options?.onError?.(error);
    },
  });
}

// 批量刷新相关数据
export function useRefreshImageRecordsData() {
  const queryClient = useQueryClient();

  return useCallback((customerId: number) => {
    // 刷新影像记录列表
    queryClient.invalidateQueries({
      queryKey: imageRecordsKeys.list(customerId),
      exact: false
    });

    // 刷新预约记录列表
    queryClient.invalidateQueries({
      queryKey: imageRecordsKeys.reservations(customerId),
      exact: false
    });

    console.log('🔄 已刷新影像记录相关数据');
  }, [queryClient]);
}

3.4.3 乐观更新实现

对于用户操作,我们实现了乐观更新来提升用户体验:

typescript 复制代码
// 乐观更新示例
export function useUpdateCureEmrInfoMutation(options?: {
  onSuccess?: (data: UpdateCureEmrInfoResponse) => void;
  onError?: (error: any) => void;
}) {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: updateCureEmrInfo,
    onMutate: async (variables) => {
      // 取消正在进行的查询,避免冲突
      await queryClient.cancelQueries({
        queryKey: caseKeys.list(variables.body.customerId)
      });

      // 保存当前数据
      const previousData = queryClient.getQueryData(
        caseKeys.list(variables.body.customerId)
      );

      // 乐观更新
      queryClient.setQueryData(
        caseKeys.list(variables.body.customerId),
        (old: any) => {
          if (!old) return old;

          // 更新列表中的对应项
          return {
            ...old,
            data: {
              ...old.data,
              resultList: old.data.resultList.map((item: any) =>
                item.id === variables.body.id
                  ? { ...item, ...variables.body }
                  : item
              )
            }
          };
        }
      );

      return { previousData };
    },
    onError: (error, variables, context) => {
      // 回滚到之前的数据
      if (context?.previousData) {
        queryClient.setQueryData(
          caseKeys.list(variables.body.customerId),
          context.previousData
        );
      }

      const message = error?.response?.data?.status_msg
        || error?.message
        || '更新病历信息失败';
      Alert.alert('错误', message);
      options?.onError?.(error);
    },
    onSuccess: (data, variables) => {
      // 刷新数据确保一致性
      queryClient.invalidateQueries({
        queryKey: caseKeys.list(variables.body.customerId),
        exact: false
      });

      showSuccessAlert('病历信息更新成功');
      options?.onSuccess?.(data);
    },
  });
}

3.5 错误处理与重试机制

3.5.1 统一错误处理

我们实现了统一的错误处理机制:

typescript 复制代码
// utils/request.ts
// 响应拦截器
request.interceptors.response.use(
  (response) => {
    refreshFailCount = 0; // 成功时重置
    // 直接返回业务数据
    return response.data;
  },
  async (error: AxiosError) => {
    const originalRequest = error.config as AxiosRequestConfig & { _retry?: boolean };
    const status = error.response?.status;

    // 统一处理错误信息
    const getErrorMessage = (err: AxiosError): string => {
      if (err.response?.data) {
        const data = err.response.data as any;
        return data.status_msg || data.message || JSON.stringify(data);
      }
      return err.message || '请求发生未知错误';
    };

    const errorMessage = getErrorMessage(error);

    // 401 处理:自动刷新 token
    if (status === 401 && !originalRequest._retry && refreshFailCount < MAX_REFRESH_ATTEMPTS) {
      if (originalRequest.url?.includes('/api/do_refresh_token')) {
        // 如果是刷新token的请求失败,直接处理
        await AuthManager.handleExpired('Token刷新失败,请重新登录');
        return Promise.reject(new ApiError('Token刷新失败', 401));
      }

      isRefreshing = true;
      refreshFailCount++;
      try {
        const refreshToken = await getRefreshToken();
        if (!refreshToken) {
          throw new ApiError('未找到刷新令牌,请重新登录', 401);
        }

        // 调用刷新函数
        const response = await doRefreshTokenUsingPost(refreshToken);

        if (response && response.status_code === 0 && response.data) {
          const { access_token, refresh_token } = response.data;
          await setAccessToken(access_token);
          await setRefreshToken(refresh_token);

          // 刷新token成功后,尝试恢复用户状态
          try {
            const { ensureUserState } = await import('@/utils/auth/userStateManager');
            await ensureUserState();
            console.log('✅ Token刷新后用户状态已恢复');
          } catch (error) {
            console.warn('⚠️ Token刷新后用户状态恢复失败:', error);
          }

          retryQueue.forEach((cb) => cb(access_token));
          retryQueue = [];

          originalRequest.headers = { ...originalRequest.headers, Authorization: `Bearer ${access_token}` };
          return request(originalRequest);
        } else {
          throw new ApiError(response?.status_msg || '刷新token失败', 401);
        }
      } catch (refreshError: any) {
        retryQueue.forEach((cb) => cb(''));
        retryQueue = [];

        if (refreshFailCount >= MAX_REFRESH_ATTEMPTS) {
          await AuthManager.handleExpired('多次刷新token失败,请重新登录');
        }
        return Promise.reject(new ApiError(refreshError.message || '刷新token时出错', 401));
      } finally {
        isRefreshing = false;
      }
    }

    // 处理其他已知错误
    if (isAuthError(errorMessage)) {
      await AuthManager.handleExpired(errorMessage);
    } else {
      AuthManager.handleError(errorMessage);
    }

    // 最终抛出统一格式的错误
    return Promise.reject(new ApiError(errorMessage, status));
  }
);

3.5.2 认证错误处理

typescript 复制代码
// utils/auth.ts
/**
 * 认证管理器
 *
 * 处理token过期、自动跳转登录页、清除用户状态等认证相关的全局操作
 * 支持防重复处理,避免并发请求导致的重复通知和操作
 */

import { useUIStore, useUserStore } from '@/stores';
import { router } from 'expo-router';

// 防重复处理的状态管理
let isHandlingTokenExpired = false;
let lastAuthErrorTime = 0;
let lastTokenExpiredTime = 0;
let lastErrorMessage = '';

// 防重复时间窗口(毫秒)
const AUTH_ERROR_DEBOUNCE_TIME = 3000; // 3秒内相同错误只显示一次
const TOKEN_EXPIRED_DEBOUNCE_TIME = 5000; // 5秒内只处理一次token过期

/**
 * 认证错误处理器(带防重复)
 * @param message 错误信息
 */
export function handleAuthError(message: string) {
  const now = Date.now();

  // 防重复:相同错误信息在时间窗口内只显示一次
  if (message === lastErrorMessage && now - lastAuthErrorTime < AUTH_ERROR_DEBOUNCE_TIME) {
    console.log('跳过重复的认证错误通知:', message);
    return;
  }

  lastAuthErrorTime = now;
  lastErrorMessage = message;

  const { addNotification } = useUIStore.getState();

  // 显示错误通知
  addNotification({
    type: 'error',
    title: '请求失败',
    message: message || '网络请求出现错误',
  });

  console.log('显示认证错误通知:', message);
}

/**
 * Token过期处理器(带防重复)
 * @param reason 过期原因
 */
export async function handleTokenExpired(reason?: string) {
  const now = Date.now();

  // 防重复:在时间窗口内只处理一次token过期
  if (isHandlingTokenExpired || (now - lastTokenExpiredTime < TOKEN_EXPIRED_DEBOUNCE_TIME)) {
    console.log('跳过重复的token过期处理:', reason);
    return;
  }

  isHandlingTokenExpired = true;
  lastTokenExpiredTime = now;

  try {
    console.log('🚪 开始处理token过期:', reason);

    // 清除用户状态
    const { logout } = useUserStore.getState();
    await logout();

    // 清除所有查询缓存
    const { queryManager } = await import('@/utils/queryUtils');
    queryManager.clearAllCache();

    // 跳转到登录页
    router.replace('/login');

    // 显示过期通知
    const { addNotification } = useUIStore.getState();
    addNotification({
      type: 'warning',
      title: '登录已过期',
      message: reason || '您的登录状态已过期,请重新登录',
      duration: 5000,
    });

    console.log('✅ Token过期处理完成');
  } catch (error) {
    console.error('❌ Token过期处理失败:', error);
  } finally {
    isHandlingTokenExpired = false;
  }
}

/**
 * 认证管理器类
 */
export class AuthManager {
  static async handleExpired(reason?: string) {
    await handleTokenExpired(reason);
  }

  static handleError(message: string) {
    handleAuthError(message);
  }
}

3.5.3 React Query 错误处理

typescript 复制代码
// 在 React Query 中处理错误
export function useCustomerEmr(customerId: number, loginOrgId: number) {
  return useQuery({
    queryKey: caseKeys.list(customerId),
    queryFn: async () => {
      const response = await getCustomerEmr({
        body: { customerId, loginOrgId },
      });
      return response;
    },
    enabled: !!customerId && customerId > 0 && !!loginOrgId && loginOrgId > 0,
    staleTime: 0,
    retry: (failureCount, error) => {
      // 对于认证错误,不重试
      if (error?.status === 401) {
        return false;
      }
      // 对于网络错误,最多重试3次
      return failureCount < 3;
    },
    retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
    onError: (error) => {
      console.error('获取患者病历失败:', error);
      // 错误处理由全局拦截器处理
    },
  });
}

3.6 性能优化策略

3.6.1 查询优化

我们通过多种策略优化查询性能:

typescript 复制代码
// 1. 启用查询的条件控制
export function useCustomerEmr(customerId: number, loginOrgId: number) {
  return useQuery({
    queryKey: caseKeys.list(customerId),
    queryFn: async () => {
      const response = await getCustomerEmr({
        body: { customerId, loginOrgId },
      });
      return response;
    },
    enabled: !!customerId && customerId > 0 && !!loginOrgId && loginOrgId > 0,
    staleTime: 5 * 60 * 1000, // 5分钟内数据保持新鲜
    gcTime: 10 * 60 * 1000, // 10分钟后垃圾回收
    refetchOnWindowFocus: false, // 窗口聚焦时不自动刷新
    refetchOnMount: false, // 组件挂载时不自动刷新
  });
}

// 2. 预取数据
export function usePrefetchPatientData() {
  const queryClient = useQueryClient();

  return useCallback((customerId: number, loginOrgId: number) => {
    // 预取患者病历数据
    queryClient.prefetchQuery({
      queryKey: caseKeys.list(customerId),
      queryFn: () => getCustomerEmr({ body: { customerId, loginOrgId } }),
      staleTime: 5 * 60 * 1000,
    });

    // 预取影像记录数据
    queryClient.prefetchQuery({
      queryKey: imageRecordsKeys.list(customerId),
      queryFn: () => getImageRecordsList({ body: { customerId } }),
      staleTime: 5 * 60 * 1000,
    });
  }, [queryClient]);
}

// 3. 数据转换优化
export function useImageRecordsData(customerId: number) {
  const { data, isLoading, error, refetch } = useImageRecordsList(customerId);

  // 使用 useMemo 优化数据转换
  const processedData = useMemo(() => {
    if (!data?.data?.resultList) return [];

    return data.data.resultList.map((record: any) => ({
      ...record,
      // 处理图片URL
      imageUrl: record.imageUrl ? `${API_BASE_URL}${record.imageUrl}` : null,
      // 格式化时间
      createTime: dayjs(record.createTime).format('YYYY-MM-DD HH:mm'),
      // 计算文件大小
      fileSize: record.fileSize ? formatFileSize(record.fileSize) : '未知',
    }));
  }, [data]);

  return {
    data: processedData,
    isLoading,
    error,
    refetch,
  };
}

3.6.2 状态订阅优化

typescript 复制代码
// 精确订阅优化
const PatientDetailScreen = () => {
  // 只订阅需要的状态,避免不必要的重渲染
  const { user } = useUserStore(state => ({ user: state.user }));
  const { modals } = useUIStore(state => ({ modals: state.modals }));

  // 使用选择器函数进行精确订阅
  const isGlobalLoading = useUIStore(state => state.loading.global);
  const selectedDepartment = useUIStore(state => state.selectedDepartment);

  // 使用 useCallback 优化函数引用
  const handleEdit = useCallback(() => {
    router.push({
      pathname: '/patient/edit/[id]',
      params: { id: patientId }
    });
  }, [patientId]);

  return (
    // 组件实现
  );
};

// 使用 React.memo 优化组件渲染
const PatientCard = React.memo(({ patient, onPress }: PatientCardProps) => {
  return (
    <TouchableOpacity onPress={onPress}>
      {/* 患者卡片内容 */}
    </TouchableOpacity>
  );
});

3.6.3 内存管理

typescript 复制代码
// 组件卸载时清理状态
const PatientListScreen = () => {
  const { clearPageState } = useUIStore();

  useEffect(() => {
    return () => {
      // 组件卸载时清理页面状态
      clearPageState('patientList');
    };
  }, [clearPageState]);

  return (
    // 组件实现
  );
};

// 定期清理过期缓存
export class CacheManager {
  private static instance: CacheManager;
  private cleanupInterval: NodeJS.Timeout | null = null;

  static getInstance(): CacheManager {
    if (!CacheManager.instance) {
      CacheManager.instance = new CacheManager();
    }
    return CacheManager.instance;
  }

  startPeriodicCleanup() {
    // 每30分钟清理一次过期缓存
    this.cleanupInterval = setInterval(() => {
      this.cleanupExpiredCache();
    }, 30 * 60 * 1000);
  }

  private cleanupExpiredCache() {
    const queryClient = queryManager.getQueryClient();
    if (!queryClient) return;

    // 清理过期的查询
    queryClient.getQueryCache().findAll().forEach(query => {
      const lastUpdated = query.state.dataUpdatedAt;
      const now = Date.now();
      const age = now - lastUpdated;

      // 清理超过1小时的缓存
      if (age > 60 * 60 * 1000) {
        queryClient.removeQueries({ queryKey: query.queryKey });
      }
    });

    console.log('🧹 定期缓存清理完成');
  }

  stopPeriodicCleanup() {
    if (this.cleanupInterval) {
      clearInterval(this.cleanupInterval);
      this.cleanupInterval = null;
    }
  }
}

3.7 状态管理最佳实践

3.7.1 状态设计原则

  1. 单一职责原则:每个 store 只负责特定领域的状态
  2. 最小化状态:只存储必要的状态,避免冗余
  3. 状态不可变性:使用不可变更新模式
  4. 类型安全:完整的 TypeScript 类型定义
  5. 性能优化:精确订阅和选择性更新

3.7.2 状态更新模式

typescript 复制代码
// 1. 基础状态更新
const updateUser = (updates: Partial<User>) => {
  const currentUser = get().user;
  if (currentUser) {
    set({ user: { ...currentUser, ...updates } });
  }
};

// 2. 复杂状态更新
const addStatistic = (stat: WorkStatistic) => set((state) => {
  const existingIndex = state.statistics.findIndex(
    s => s.indicator === stat.indicator && s.dimension === stat.dimension
  );

  let newStatistics;
  if (existingIndex >= 0) {
    // 更新现有统计
    newStatistics = [...state.statistics];
    newStatistics[existingIndex] = { ...stat, lastUpdated: dayjs().toISOString() };
  } else {
    // 添加新统计
    newStatistics = [...state.statistics, { ...stat, lastUpdated: dayjs().toISOString() }];
  }

  return {
    statistics: newStatistics,
    lastFetchTime: dayjs().toISOString(),
    error: null
  };
});

// 3. 批量状态更新
const resetAppState = () => set({
  isLoading: false,
  error: null,
  isBooted: false,
  splashLoading: true,
  hasTriedAutoLogin: false,
  lastLoginTime: null,
  lastUserId: null,
});

3.7.3 状态持久化策略

typescript 复制代码
// 1. 选择性持久化
export const useAppStore = create<AppState>()(
  persist(
    (set, get) => ({
      // store 实现
    }),
    {
      name: 'app-storage',
      storage: createJSONStorage(() => AsyncStorage),
      partialize: (state) => ({
        // 只持久化必要的状态
        theme: state.theme,
        language: state.language,
        isInstalled: state.isInstalled,
        // 不持久化敏感信息如 token
      }),
    }
  )
);

// 2. 敏感信息分离存储
export const useUserStore = create<UserState>()(
  persist(
    (set, get) => ({
      // store 实现
    }),
    {
      name: 'user-storage',
      storage: createJSONStorage(() => AsyncStorage),
      partialize: (state) => ({
        // 只持久化用户信息,token 由 SecureStore 管理
        isAuthenticated: state.isAuthenticated,
        user: state.user,
      }),
    }
  )
);

3.7.4 状态调试与监控

typescript 复制代码
// 开发环境下的状态调试
if (__DEV__) {
  // Zustand 状态调试
  useAppStore.subscribe(
    (state) => console.log('App State Changed:', state),
    (state) => state.theme
  );

  // React Query 状态调试
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: {
        onError: (error) => {
          console.error('Query Error:', error);
        },
        onSuccess: (data) => {
          console.log('Query Success:', data);
        },
      },
    },
  });
}

// 状态监控工具
export class StateMonitor {
  static logStateChange(storeName: string, prevState: any, nextState: any) {
    if (__DEV__) {
      console.log(`[${storeName}] State Changed:`, {
        previous: prevState,
        next: nextState,
        changes: this.getStateChanges(prevState, nextState),
      });
    }
  }

  private static getStateChanges(prev: any, next: any): any {
    const changes: any = {};

    Object.keys(next).forEach(key => {
      if (prev[key] !== next[key]) {
        changes[key] = {
          from: prev[key],
          to: next[key],
        };
      }
    });

    return changes;
  }
}

3.8.1 核心架构特点

  1. 双状态管理:Zustand 管理客户端状态,React Query 管理服务端状态
  2. 类型安全:完整的 TypeScript 类型定义和类型检查
  3. 性能优化:精确订阅、缓存策略、乐观更新
  4. 错误处理:统一的错误处理机制和重试策略
  5. 状态持久化:智能的持久化策略和敏感信息保护

3.8.2 最佳实践总结

  1. 状态分类:清晰区分客户端状态和服务端状态
  2. 缓存策略:合理的缓存时间和失效策略
  3. 错误处理:统一的错误处理和用户友好的错误提示
  4. 性能优化:精确订阅、数据预取、内存管理
  5. 开发体验:完善的调试工具和状态监控

3.8.3 技术优势

  • 开发效率:类型安全的状态管理减少运行时错误
  • 用户体验:乐观更新和智能缓存提升响应速度
  • 维护性:清晰的状态架构便于功能扩展和维护
  • 性能:精确的状态订阅和缓存策略优化渲染性能

这套状态管理架构为医疗应用提供了稳定、高效、可维护的数据管理基础,确保了应用的可靠性和用户体验。


下一章预告:第四章将详细介绍组件化架构与设计系统,包括组件分层设计、NativeWind + Tailwind 样式系统、以及 Storybook 组件文档等核心内容。

相关推荐
古夕7 小时前
Vue3 + vue-query 的重复请求问题解决记录
前端·javascript·vue.js
不知名程序员第二部7 小时前
前端-业务-架构
前端·javascript·代码规范
Bug生产工厂7 小时前
React支付组件设计与封装:从基础组件到企业级解决方案
前端·react.js·typescript
小喷友7 小时前
阶段三:进阶(Rust 高级特性)
前端·rust
华仔啊7 小时前
面试官:请解释一下 JS 的 this 指向。别慌,看完这篇让你对答如流!
前端·javascript
Strayer7 小时前
Tauri2.0打包构建报错
前端
小高0077 小时前
💥💥💥前端“隐藏神技”:15 个高效却鲜为人知的 Web API 大起底
前端·javascript
flyliu7 小时前
再再次去搞懂事件循环
前端·javascript
艾小码7 小时前
还在拍脑袋估工时?3个技巧让你告别加班和延期!
前端·敏捷开发