在上一章中,我们深入探讨了 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 状态管理原则
我们遵循以下核心原则来设计状态管理架构:
- 单一数据源:每种状态只有一个权威来源
- 状态不可变性:状态更新必须通过纯函数进行
- 状态持久化:关键状态需要持久化到本地存储
- 类型安全:所有状态都有完整的 TypeScript 类型定义
- 性能优化:合理使用缓存和订阅优化
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 状态设计原则
- 单一职责原则:每个 store 只负责特定领域的状态
- 最小化状态:只存储必要的状态,避免冗余
- 状态不可变性:使用不可变更新模式
- 类型安全:完整的 TypeScript 类型定义
- 性能优化:精确订阅和选择性更新
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 核心架构特点
- 双状态管理:Zustand 管理客户端状态,React Query 管理服务端状态
- 类型安全:完整的 TypeScript 类型定义和类型检查
- 性能优化:精确订阅、缓存策略、乐观更新
- 错误处理:统一的错误处理机制和重试策略
- 状态持久化:智能的持久化策略和敏感信息保护
3.8.2 最佳实践总结
- 状态分类:清晰区分客户端状态和服务端状态
- 缓存策略:合理的缓存时间和失效策略
- 错误处理:统一的错误处理和用户友好的错误提示
- 性能优化:精确订阅、数据预取、内存管理
- 开发体验:完善的调试工具和状态监控
3.8.3 技术优势
- 开发效率:类型安全的状态管理减少运行时错误
- 用户体验:乐观更新和智能缓存提升响应速度
- 维护性:清晰的状态架构便于功能扩展和维护
- 性能:精确的状态订阅和缓存策略优化渲染性能
这套状态管理架构为医疗应用提供了稳定、高效、可维护的数据管理基础,确保了应用的可靠性和用户体验。
下一章预告:第四章将详细介绍组件化架构与设计系统,包括组件分层设计、NativeWind + Tailwind 样式系统、以及 Storybook 组件文档等核心内容。