分享一个基于 Expo + React Native 的真实医疗项目架构实践,涵盖现代化技术栈、组件化设计、原生模块集成等核心技术点。
最近参与开发了一个医疗行业的移动端管理应用,主要服务于医疗机构的日常管理需求,包括患者管理、预约系统、数据统计等核心功能。项目采用 Expo + React Native 技术栈,在保证开发效率的同时,实现了接近原生的用户体验。
{
"核心框架": "Expo SDK 53 + React Native 0.79",
"路由方案": "Expo Router (文件系统路由)",
"状态管理": "Zustand + React Query",
"UI 组件库": "Ant Design React Native",
"样式方案": "NativeWind (Tailwind CSS)",
"表单处理": "React Hook Form + Yup",
"开发工具": "TypeScript + Storybook",
"原生功能": "自定义 Expo 模块"
}
采用 Expo Router 的文件系统路由,目录结构清晰且符合现代前端开发习惯:
├── app/ # 路由页面
│ ├── (tabs)/ # Tab 导航页面
│ │ ├── index.tsx # 首页
│ │ ├── ai.tsx # AI 助手
│ │ ├── message.tsx # 消息中心
│ │ └── profile.tsx # 个人中心
│ ├── patient/ # 患者管理模块
│ ├── reservation/ # 预约管理模块
│ └── login.tsx # 登录页面
├── components/ # 通用组件库
├── stores/ # 状态管理
├── utils/ # 工具函数
├── modules/ # 自定义原生模块
└── docs/ # 完整文档体系
结合 Zustand 和 React Query 实现了完整的数据流管理:
// stores/dataStatsStore.ts - 业务状态管理
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface DataStatsState {
selectedTimeRange: string;
departmentId: string;
setTimeRange: (range: string) => void;
setDepartment: (id: string) => void;
}
export const useDataStatsStore = create<DataStatsState>()(
persist(
(set) => ({
selectedTimeRange: 'week',
departmentId: '',
setTimeRange: (range) => set({ selectedTimeRange: range }),
setDepartment: (id) => set({ departmentId: id }),
}),
{ name: 'data-stats-storage' }
)
);
// hooks/useDataStatistics.ts - 数据获取层
import { useQuery } from '@tanstack/react-query';
export const useDataStatistics = () => {
const { departmentId, selectedTimeRange } = useDataStatsStore();
return useQuery({
queryKey: ['dataStats', departmentId, selectedTimeRange],
queryFn: () => fetchDataStatistics({ departmentId, selectedTimeRange }),
staleTime: 5 * 60 * 1000, // 5分钟缓存
refetchOnWindowFocus: true,
});
};
架构优势:
✅ 状态分离:UI 状态用 Zustand,服务端状态用 React Query
✅ 自动缓存:智能缓存机制,减少不必要的网络请求
✅ 数据同步:自动重试、后台刷新、实时同步
✅ 类型安全:TypeScript 全覆盖,编译时错误检查
构建了完整的组件库,每个组件都有对应的 Storybook 文档:
// components/business/DataStatsGrid.tsx
interface DataStatsGridProps {
data?: StatsData;
isLoading?: boolean;
onRefresh?: () => void;
}
export const DataStatsGrid: React.FC<DataStatsGridProps> = ({
data,
isLoading,
onRefresh
}) => {
return (
<View className="bg-white rounded-lg p-4 mx-4 shadow-sm">
<View className="flex-row justify-between items-center mb-4">
<Text className="text-lg font-semibold text-gray-800">数据统计</Text>
<TouchableOpacity onPress={onRefresh} disabled={isLoading}>
<Ionicons
name="refresh"
size={20}
color={isLoading ? '#ccc' : '#666'}
/>
</TouchableOpacity>
</View>
<View className="flex-row flex-wrap">
{statsItems.map((item, index) => (
<StatsItem
key={index}
title={item.title}
value={data?.[item.key] || 0}
icon={item.icon}
trend={item.trend}
/>
))}
</View>
</View>
);
};
设计特色:
原子化组件:可复用、可组合的组件设计
文档驱动:71% 组件覆盖率,59+ Storybook Stories
TypeScript:完整的类型定义和 Props 约束
响应式:基于 NativeWind 的响应式布局
针对特殊业务需求,开发了自定义的 Expo 模块:
// modules/my-module/src/WxModule.ts
import { NativeModule, requireNativeModule } from 'expo';
export interface WxModuleEvents {
onWeworkAuthResult: (result: { code?: string; error?: string }) => void;
}
class WxModule extends NativeModule<WxModuleEvents> {
// 第三方平台登录
async thirdPartyAuth(corpId: string, agentId: string): Promise<boolean> {
return await this.nativeModule.thirdPartyAuth(corpId, agentId);
}
// 注册深度链接处理
registerWeworkCallback(callback: (result: any) => void): void {
this.addListener('onWeworkAuthResult', callback);
}
}
export default requireNativeModule('WxModule');
原生集成特色:
第三方登录:完整的第三方 SDK 集成,支持免密登录
极光推送:智能推送服务,支持多端消息同步
深度链接:应用内跳转和外部唤起支持
⚡ 性能优化:原生模块处理计算密集型任务
// app/(tabs)/index.tsx
export default function HomePage() {
const { data: statsData, isLoading, refetch } = useDataStatistics();
const { selectedDepartment } = useDepartmentStore();
return (
<ParallaxScrollView
headerBackgroundColor={{ light: '#D0EFFF', dark: '#353636' }}
headerImage={<Image source={require('@/assets/images/hero-bg.png')} />}
>
{/* 部门选择器 */}
<DepartmentSelector />
{/* 搜索栏 */}
<SearchBar placeholder="搜索内容..." />
{/* 主功能网格 */}
<MainFunctionGrid />
{/* 数据统计 */}
<DataStatsGrid
data={statsData}
isLoading={isLoading}
onRefresh={refetch}
/>
{/* AI 助手功能卡片 */}
<AIAssistantCard />
</ParallaxScrollView>
);
}
设计亮点:
视觉层次:渐变背景 + 卡片式布局
数据驱动:实时数据统计和趋势展示
AI 集成:智能助手功能入口
快速操作:搜索、筛选、跳转一体化
// 患者信息表单
import { useForm, Controller } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
const patientSchema = yup.object({
name: yup.string().required('请输入患者姓名'),
idCard: yup.string().matches(ID_CARD_REGEX, '身份证格式不正确'),
phone: yup.string().matches(PHONE_REGEX, '手机号格式不正确'),
});
export const PatientForm = () => {
const { control, handleSubmit, formState: { errors } } = useForm({
resolver: yupResolver(patientSchema),
defaultValues: {
name: '',
idCard: '',
phone: '',
},
});
const { mutate: createPatient, isLoading } = useMutation({
mutationFn: createPatientApi,
onSuccess: () => {
Toast.show({ type: 'success', text1: '患者信息已保存' });
router.back();
},
});
return (
<View className="p-4">
<Controller
control={control}
name="name"
render={({ field: { onChange, value } }) => (
<FormField
label="患者姓名"
value={value}
onChangeText={onChange}
error={errors.name?.message}
/>
)}
/>
{/* 其他表单项... */}
</View>
);
};
// 使用 Expo Image 优化图片加载
import { Image } from 'expo-image';
export const OptimizedImage = ({ source, ...props }) => (
<Image
source={source}
placeholder={{ blurhash: 'L6PZfSi_.AyE_3t7t7R**0o#DgR4' }}
contentFit="cover"
transition={200}
{...props}
/>
);
// 大数据列表优化
import { FlashList } from '@shopify/flash-list';
export const PatientList = ({ patients }) => (
<FlashList
data={patients}
renderItem={({ item }) => <PatientCard patient={item} />}
estimatedItemSize={80}
keyExtractor={(item) => item.id}
onEndReachedThreshold={0.1}
onEndReached={loadMorePatients}
/>
);
// 使用 NativeWind 实现响应式设计
export const StatsGrid = () => (
<View className="flex-row flex-wrap">
{statsData.map((stat) => (
<View
key={stat.id}
className="w-1/2 md:w-1/4 p-2" // 移动端 2 列,平板 4 列
>
<StatsCard {...stat} />
</View>
))}
</View>
);
// 平台特定的状态栏处理
import { Platform } from 'react-native';
import { StatusBar } from 'expo-status-bar';
export const DynamicStatusBar = ({ route }) => {
const getStatusBarStyle = () => {
if (Platform.OS === 'ios') {
return route.name === 'login' ? 'light' : 'dark';
}
return 'auto';
};
return <StatusBar style={getStatusBarStyle()} />;
};
// components/DataStatsGrid.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { DataStatsGrid } from './DataStatsGrid';
const meta: Meta<typeof DataStatsGrid> = {
title: 'Business/DataStatsGrid',
component: DataStatsGrid,
parameters: {
docs: {
description: {
component: '数据统计网格组件,展示各类业务数据统计',
},
},
},
};
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
data: mockStatsData,
isLoading: false,
},
};
export const Loading: Story = {
args: {
isLoading: true,
},
};
// 使用 openapi-ts-request 自动生成类型
import { request } from '@/utils/request';
import type { DataStatisticsResponse } from '@/types/api';
export const dataStatsApi = {
// 获取数据统计
getDataStatistics: (params: StatsParams): Promise<DataStatisticsResponse> =>
request.get('/api/data-statistics', { params }),
// 获取部门列表
getDepartments: (): Promise<Department[]> =>
request.get('/api/departments'),
};
// utils/auth.ts
import * as SecureStore from 'expo-secure-store';
export const tokenManager = {
async setToken(token: string): Promise<void> {
await SecureStore.setItemAsync('auth_token', token);
},
async getToken(): Promise<string | null> {
return await SecureStore.getItemAsync('auth_token');
},
async removeToken(): Promise<void> {
await SecureStore.deleteItemAsync('auth_token');
},
};
// utils/permissions.ts
import * as Notifications from 'expo-notifications';
export const requestNotificationPermission = async () => {
const { status: existingStatus } = await Notifications.getPermissionsAsync();
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync();
return status === 'granted';
}
return true;
};
⚡ 启动性能:冷启动时间 < 3s
包体积:Android APK < 50MB
组件复用率:71% 组件已文档化
代码覆盖率:核心业务逻辑 > 80%
用户体验:统一的设计语言和交互模式
开发效率:组件化开发,新功能开发周期缩短 40%
多端一致性:iOS/Android 体验一致性 > 95%
️ 维护成本:模块化架构,维护成本降低 60%
✅ 状态分离:UI 状态与服务端状态分离管理
✅ 组件化:原子化组件设计,提高复用性
✅ 类型安全:TypeScript 全面覆盖,减少运行时错误
✅ 文档驱动:Storybook 保证组件质量和可维护性
✅ 懒加载:路由级别的代码分割
✅ 缓存策略:智能的数据缓存和更新机制
✅ 图片优化:WebP 格式 + CDN + 懒加载
✅ 包体优化:Tree-shaking + 动态导入
✅ 热重载:Expo Dev Client 提供原生级热重载
✅ 调试工具:Flipper + React Query DevTools
✅ 自动化:ESLint + Prettier + 自动化 API 类型生成
✅ 团队协作:统一的代码规范和组件文档
微前端架构:模块化拆分,支持独立部署
离线支持:Service Worker + 本地存储同步
AI 增强:更深度的 AI 功能集成
性能监控:Sentry + 自定义性能指标
自动化测试:E2E 测试覆盖率提升
通过这个项目的实践,深刻体会到 Expo + React Native 在企业级应用开发中的强大能力。合理的架构设计、现代化的技术栈选择,以及完善的工程化配置,是项目成功的关键因素。
希望这篇分享能对正在使用或考虑使用 Expo 开发的朋友们有所帮助。如果你有任何问题或想要了解更多技术细节,欢迎在评论区交流讨论!
技术栈标签:Expo
React Native
TypeScript
Zustand
React Query
医疗应用
移动开发
如果这篇文章对你有帮助,别忘了点赞和收藏哦!也欢迎关注我,后续会分享更多前端技术实践经验。
————————————————
版权声明:本文为CSDN博主「疯狂求学的小白」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_60678785/article/details/149019316