尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

React Native鸿蒙跨平台开发实践:医疗预就诊应用架构与排障

React Native鸿蒙跨平台开发实践:医疗预就诊应用架构与排障 去年下半年我们团队接了一个医疗信息化的活儿给本地一家三甲医院做“就诊准备”功能。核心诉求很直白患者别一窝蜂跑到医院再排队问诊先在手机上把症状描述清楚、把病史填完整、把想看的医生选好到院之后直接做检查或者进诊室整个预就诊链路能省掉一半时间。这个需求听起来简单落地却有讲究——医院希望一套代码同时覆盖安卓、iOS还要考虑鸿蒙设备毕竟现在医院里用鸿蒙手机的患者越来越多了。我们最终选了React Native针对鸿蒙做了深度适配。从工程搭建到功能闭环再到启动白屏、模拟器调试这些坑都踩了一遍。这篇文章不写“广告软文”只分享我们这个项目在React Native鸿蒙跨平台开发上的真实思路、核心代码和排障记录给正在做同类医疗场景的朋友一个参考。1. 项目整体设计与思路拆解1.1 为什么选 React Native 做鸿蒙跨平台开发医院项目的选型不像互联网公司那么随意最看重的是稳定性和可维护性。一开始团队里也有声音说用Flutter或者干脆三端原生开发。但仔细盘过需求之后React Native成了最合适的选择原因主要有三个。第一React Native的开发模式更贴近Web思维。我们医院信息科的同事大多会React后面维护成本低。第二React Native生态里的表单、状态管理、网络请求等库非常成熟做“症状描述病史填写医生选择”这种重表单场景开发速度比原生要快不少。第三也是最重要的鸿蒙这边已经有社区方案支持React Native可以通过桥接层把RN的JS组件渲染为鸿蒙的原生组件这就意味着我们写的业务代码可以同时跑在Android、iOS和鸿蒙上。这里要强调一下鸿蒙系统有“兼容安卓”的旧版本也有走独立架构的新版本如果想在纯血鸿蒙上跑通应用就必须用官方或社区提供的RN鸿蒙适配层不能默认“鸿蒙能跑安卓APK”就万事大吉。我们项目一开始就定位成“React Native 鸿蒙原生工程”把鸿蒙作为一个一等目标平台来对待而不是等安卓跑通了再补鸿蒙这样减少了很多返工。1.2 核心需求分析症状描述、病史填写、医生选择预就诊应用说白了就是三个动作告诉医生你哪里不舒服、你过去有什么健康问题、你想找谁看。这三个动作对应的业务逻辑完全不同不能简单做成三个页面。症状描述看起来简单但用户不是医生很难用专业术语描述问题。所以我们在设计时做了一个“主诉伴随症状标签”的组合录入用户先输入一句自然语言比如“右侧腹部隐痛持续三天”再勾选系统预设的常见症状标签比如“恶心、发热、按压痛”。这样做的好处是后端拿到结构化标签后可以做初步分诊推送到对应科室也能帮用户自动匹配擅长这类症状的医生。病史填写则是整个应用里最敏感、最需要严谨对待的模块。既往病史、过敏史、手术史、正在服用的药物、家族病史……这些数据一旦出错轻则影响诊断重则会出医疗事故。所以这个模块我们没有用自由文本而是全部做成结构化选项比如手术史支持年份、医院、诊断名称的组输入过敏史区分“药物/食物/其他”并且每一项后面都标注“若确认请勾选”。数据层面则做了本地加密存储和权限校验确保不落地明文。医生选择更像是电商产品里的“选商品 下单”。我们按科室分类再按医生的职称、擅长领域、排班日期做筛选点击医生后能看到剩余号源数。跟传统挂号App相比这里的特殊点在于预就诊应用不是直接支付挂号费而是把前面填好的症状摘要、病史摘要作为“就诊前信息”一起提交医生接诊前就能提前浏览所以医生选择模块的推荐逻辑会综合症状分析和号源情况。1.3 技术架构与工程组织整个项目的技术栈分为三层业务层是React Native组件和页面负责症状表单、病史表单、医生列表、就诊人信息和提交结果页。状态管理层用了Redux Toolkit因为表单数据分散在多个页面需要集中管理且要支持刷新App后还能恢复未完成的草稿。底层是原生工程安卓和iOS各一套鸿蒙单独一套三者都通过React Native的桥接协议挂载同一个JSBundle。目录结构大致是这样src/ ├── app/ # 应用入口导航配置 ├── features/ │ ├── symptoms/ # 症状描述模块 │ ├── history/ # 病史填写模块 │ └── doctor-select/ # 医生选择模块 ├── shared/ │ ├── components/ # 跨模块通用UI组件 │ ├── hooks/ # 自定义hooks │ └── utils/ # 格式化、校验等工具 ├── store/ # Redux store配置 ├── services/ # API请求、本地存储封装 └── native/ # 鸿蒙/安卓/iOS原生工程适配这个结构最核心的思路是“按功能垂直切分”。每个feature内部包含页面组件、业务hooks、API定义和本地化文案互不干扰。因为医院项目后续大概率要加“在线问诊”“报告查询”等功能垂直分模块比水平分层更好扩展。2. 核心功能模块解析与实操要点2.1 症状描述模块表单设计与智能提示症状描述模块做了两个入口一个是从首页直接进入“填写症状”另一个是用户挂完号之后在“我的预检”里补充症状。整个表单分三步每步对应一个RN页面通过导航器串联。第一步是主诉输入。这里用了一个多行TextInput同时加了一个最大字数限制100字因为主诉需要精炼太长反而不利于医生阅读。输入的时候会去掉前后空格并过滤掉表情符号避免后端保存时出现乱码。第二步是伴随症状选择用“Wrap布局的Chip组件”实现用户点选/取消标签每个标签都对应一个标准化症状Code提交时传的是Code数组。第三步是持续时间支持“今天”“1-3天”“3天以上”“慢病反复发作”等几个大项。这里有一个非常关键的设计我们在第一步主诉输入框下方做了一个“智能推荐”区域根据用户输入的关键词联想常见症状标签。比如输入“咳嗽”推荐“咽痛、咳痰、发热、流涕”。实现思路并不复杂前端维护一份症状词典每次输入变更时做模糊匹配。考虑到医疗场景不允许随便瞎推荐如果关键词匹配不到任何内容这个区域就不渲染宁可少推荐也不能误导患者。核心代码片段如下type Symptom { code: string; label: string }; const SYMPTOM_DICT: Symptom[] [/* 从远程后端加载 */]; const getMatchedSymptoms (text: string) { const keywords text.trim().toLowerCase(); if (!keywords) return []; return SYMPTOM_DICT.filter(item item.label.includes(keywords) || keywords.includes(item.label) ).slice(0, 5); };在React Native鸿蒙适配过程中这个组件也踩过一个坑鸿蒙的TextInput在受控组件模式下如果onChangeText里频繁setState会偶发输入卡顿。后来我们把“联想推荐”的setState改成节流debounce卡顿问题明显缓解。这也是跨端开发的一个共性点——不能只看iOS和安卓鸿蒙的底层输入法事件频率跟安卓不完全一致节流是必须的。2.2 病史填写模块结构化数据与隐私安全病史填写是整个应用里数据敏感性最高的部分技术上不难难在细节做得到不到位。我们按照“既往史、过敏史、手术史、用药史、家族史”五个分区来组织表单每个分区独立折叠展开避免一屏加载太多选项给用户造成压力。每个分区都用单选/复选加上“可补充描述”的形式。比如用药史不是直接填写药名而是先点选“降压药、降糖药、抗生素、抗凝药”等常见类别再弹出一个输入框允许补充具体药物。这样做既便于结构化存储又不会限制用户的自由表达。隐私安全方面除了常规的HTTPS传输外我们把病史数据在本地做了加密存储用的不是普通的AsyncStorage而是react-native-keychain配合AES加密把敏感字段先加密再落盘。鸿蒙这边因为底层API不同我们通过原生模块封装了一个createEncryptedStore方法由鸿蒙端的Security模块实现确保即使手机丢失本地数据也无法被直接读取。有个细节值得提醒用户在填写病史时经常会误触“上一页”导致数据丢失。我们加了Redux的自动草稿同步每次表单更新都会防抖写入持久化存储用户回到页面时直接从store恢复而不是从接口重新拉取。如果草稿恢复时发现某个字段已经超过30天没更新就提示用户“为保障信息准确性请重新确认病史”这算是一个很实用的产品设计。2.3 医生选择模块科室、医生列表与号源联动医生选择模块的交互链路是科室列表 - 医生列表 - 医生详情/排班 - 确认选择。为了减少转盘请求我们预先拉取科室和医生基础数据并缓存在本地只有号源是每次实时查询。科室列表用了SectionList左侧分组展示内科、外科、儿科、妇产科等一级科室点击后进入该科室下医生列表。医生列表顶部有一个筛选条支持按“职称”“出诊时间”“剩余号源”排序。每张医生卡片上会显示用户填写的症状摘要匹配度比如“你的腹痛症状与该医生擅长领域相关度87%”这是后端根据科室、症状Code和医生标签做的一个简单推荐算法前端只需要展示。号源模块我们早期设计的是“先选择医生再选号源”后来医生反馈太多不在排班时间的咨询订单所以改成“号源优先”医生卡片默认展示最近三天可约的号源时间段用户先选时间段再确认对应医生。这样能大幅降低无效预约也避免了医生列表被没有号源的大夫占满。这里有一个针对鸿蒙的适配问题鸿蒙的Modal组件在展示医生排班弹窗时如果背后还有FlatList在滚动会出现背景穿透滚动的问题。我们最后在Android和鸿蒙上统一把弹窗外层包了一层TouchableWithoutFeedback并设置keyboardShouldPersistTapshandled解决得还算干净。2.4 跨平台兼容细节鸿蒙与Android/iOS的差异处理跨平台开发最怕的就是“安卓好好的鸿蒙一打开崩了”这类问题。我们项目里最典型的差异有三个第一导航和返回手势。React Native的stack导航在iOS支持侧滑返回但鸿蒙默认没有这个手势。我们用了react-native-screens的enableScreens和自定义手势配置在鸿蒙上开启边缘滑动返回提供和iOS一致的操作体验。如果你不做适配用户从鸿蒙右上角侧滑会没反应体感很糟糕。第二安全区适配。鸿蒙的刘海屏和底部手势条尺寸跟iPhone不一样直接用SafeAreaView在部分鸿蒙机型上会多留白。我们最后自己封装了一个useSafeAreaInsetsHook分别从三端的原生侧读取insets再通过StyleSheet注入到页面。封装后遇到不同屏幕形态只需要改一处。第三日期时间选择器。RN社区里常用的react-native-community/datetimepicker在鸿蒙上没有现成实现我们只好在鸿蒙原生工程里实现了一个DatePickerModal通过原生事件传回给JS。虽然工作量比预期多了一倍但至少保证三端都能用上原生的日期选择体验。3. 实操过程与核心环节实现3.1 环境准备React Native 鸿蒙适配的工程配置这里聊聊实际工程的搭建过程。首先我们基于最新的React Native 0.72版本初始化了一个React Native项目然后在项目根目录添加了鸿蒙原生工程目录。鸿蒙原生工程需要配置好DevEco Studio的SDK路径并且要在build.gradle中声明react-native-harmony依赖。一个常见的误区是以为只需把原生工程创建完就能自动跑通用实际上需要手工配置Bundle加载器。在鸿蒙工程里我们新增了一个ReactAbilityStage负责初始化ReactNativeHost并设置了JSBundle的加载路径。为了让开发环境支持热更新还要在鸿蒙原生侧开启DevSupportManager让Metro能够把JSBundle推送到鸿蒙设备或模拟器上。为了方便团队我们写了一个project.config.json把鸿蒙SDK版本、RN版本、编译目标统一固定。这个配置文件类似安卓的gradle/ios的Podfile是解决“我机器上能跑你机器上跑不了”这类问题的关键。建议所有做RN鸿蒙开发的同学第一步就锁定版本矩阵。3.2 核心界面与状态管理实现三个核心模块的界面都不复杂难的是状态共享。我们的做法是Redux Toolkit作为全局store表单数据都放在features下面各自的slice里。比如symptomSlice保存主诉文本和症状标签historySlice保存五个分区的病史对象doctorSlice保存选中的医生、日期和号源时间段。一个页面完成操作后不需要把数据通过路由参数传递只需要dispatch action把数据写入store下一个页面从store读取即可。这样做的好处是“下一步”和“上一步”都变得很可靠用户在任何一步退出去中间数据都不会丢失。下面是一个简化的symptomSlice写法展示同步更新和草稿保存import { createSlice, PayloadAction } from reduxjs/toolkit; interface SymptomState { chiefComplaint: string; accompaniedCodes: string[]; duration: string; draftSavedAt: number | null; } const initialState: SymptomState { chiefComplaint: , accompaniedCodes: [], duration: , draftSavedAt: null, }; const symptomSlice createSlice({ name: symptoms, initialState, reducers: { setChiefComplaint(state, action: PayloadActionstring) { state.chiefComplaint action.payload; }, toggleAccompaniedCode(state, action: PayloadActionstring) { const code action.payload; if (state.accompaniedCodes.includes(code)) { state.accompaniedCodes state.accompaniedCodes.filter(c c ! code); } else { state.accompaniedCodes.push(code); } }, markDraftSaved(state) { state.draftSavedAt Date.now(); }, }, });实际开发中我们还在中间层封装了一个withAutoDraft的高阶组件每个表单页进入时自动监听redux状态变化并有节流地调用markDraftSaved这样就不需要每个页面单独写本地存储逻辑了。3.3 数据存储与本地化医院项目的用户有大量中老年患者所以“本地化”不只是翻译还包括字体大小、多语言切换和语音输入。我们的做法是在应用内支持中文简体、中文繁体、英文三种语言全部采用i18n-js方案文案统一放在locale目录。在存储选型上我们最终没有采用纯AsyncStorage而是用了react-native-mmkv。原因很现实MMKV底层是腾讯的mmkv读写性能比AsyncStorage高一个量级而且支持加密。对于病史这种频繁写入的本地草稿MMKV的同步读取能避免很多闪屏问题。鸿蒙端也提供了MMKV的适配版本我们直接在鸿蒙原生工程里通过包管理器引入不需要额外写业务代码。要注意的是MMKV并不是React Native官方维护的库升级RN版本时容易遇到原生编译问题。我们项目里都是先升级鸿蒙适配包再升级RN版本然后再确认MMKV有没有对应版本。如果顺序反过来你会被报错折腾一整天。3.4 预就诊流程闭环从填表到预约的串联所有模块拼在一起才是完整的预就诊流程。我们设计的用户路径是用户打开App - 登录/注册 - 首页点击“就诊准备” - 填写症状描述 - 填写病史 - 系统根据症状病史推荐科室和医生 - 选择医生和时间 - 提交预就诊申请 - 生成预就诊号与二维码。这里的自动推荐不是必须的但能大大提升体验。后端接口会接收症状Code数组和病史摘要返回匹配的科室和医生标签前端再结合号源做展示。提交申请时前端会把三部分数据合并成一个JSON体附加一个schemaVersion字段方便后端以后扩展。最后生成的预就诊二维码我们用的是rn-qr-generator这个库。它在Android和iOS上表现正常但在鸿蒙上直接调用会找不到原生模块。最终我们换成了在鸿蒙原生侧使用Zing库生成二维码然后以base64图片返回给RN层展示。跨端开发就是这样看起来是个小功能每个端都得单独适配一遍。4. 常见问题与排查技巧实录4.1 启动白屏问题排查这个项目的初期阶段我们在鸿蒙模拟器上遇到过非常典型的React Native启动白屏。现象是App打开后页面一直停留在白色背景没有任何报错Metro日志显示Bundle已经加载成功。排查思路从三端出发。第一确认JSBundle是在规定时间内加载完成的如果鸿蒙设备性能比较弱Bundle加载超时会导致白屏。第二检查鸿蒙原生工程中ReactRootView的初始布局尺寸是否为0因为我们一开始把它放在了Container的未完成布局阶段导致内容渲染不出来。第三也是很多人忽略的在鸿蒙上Metro的调试地址如果设置为localhost只能用于模拟器真机需要改成电脑的局域网IP否则也会白屏。如果你的项目也遇到白屏先别急着怀疑RN框架建议按这个顺序检查初始化时序 - Bundle路径 - 调试服务地址 - 原生容器布局尺寸。我们最后是通过鸿蒙的HiLog看到ReactRootView的宽高为0才定位到问题修复了一行布局代码就解决了。4.2 鸿蒙模拟器调试注意事项鸿蒙的模拟器跟安卓模拟器体验差别很大尤其是内存占用和启动速度。我们的开发机是Mac鸿蒙模拟器需要下载对应的系统镜像第一次启动差不多要3分钟后面会快一点。调试时建议把Metro的缓存关掉否则模拟器上每次刷新都可能加载到旧Bundle。另外鸿蒙模拟器默认的Network权限管理比Android更严格开发环境如果出现网络请求失败要检查模拟器的“应用权限”里有没有给App开放网络权限。这个问题在真机上反而不容易出现因为真机安装时会弹出权限申请模拟器往往默认拒绝。日志输出方面RN的console.log可以在Metro终端看到但鸿蒙原生层的报错需要到DevEco Studio的Log窗口中查看。如果你发现JS层没有任何报错但UI表现异常一定要去Log里过滤“react”或者“ArkUI”关键词有时能看到原生侧警告信息。4.3 跨平台UI差异与样式适配三端UI差异最让人头疼的是字体渲染。相同字号在iOS偏小、Android适中、鸿蒙略大导致同样的文字在鸿蒙上经常换行错乱。我们最后统一用缩放单位在样式里通过StyleSheet.create定义fontSize: moderateScale(16)这样的形式保证不同密度屏幕上看起来一致。间距也一样。鸿蒙的默认padding与Android不同尤其是按钮。我们用了talewind语义化类名来约束间距比如p-4、mt-8但底层还是要通过Platform.select判断。写一行样式可以解决Android和iOS但鸿蒙上可能需要额外覆盖。还有一个经验在鸿蒙上尽量少用borderRadius超过屏幕一半的“圆角胶囊”组件渲染性能明显低于安卓。如果只是样式差异不用太纠结达到“视觉统一”即可没必要逐像素追求一致。4.4 常见错误速查表错误现象可能原因解决方案启动白屏Bundle加载路径错误、容器宽度为0检查鸿蒙原生加载逻辑设置正确布局Metro一直loading设备访问不到电脑IP设置debugServerHost为局域网IP网络请求全部失败鸿蒙模拟器未授予网络权限在系统设置中开启应用网络权限Modal背景可滚动手势穿透外层包TouchableWithoutFeedback本地草稿丢失AsyncStorage写入失败换成MMKV并捕获写入异常图片不显示缺少原生图片库适配使用base64或集成鸿蒙图片加载库日期选择器无法弹出RN库不支持鸿蒙在鸿蒙原生工程实现DatePicker并桥接这个速查表是我们团队内部Wiki里一直更新的内容大家遇到问题先查表解决后补充效率提升得很明显。最后再分享一个我做这个项目体会最深的事跨平台开发真正难的地方不在启动白屏也不在某个组件库不兼容而在于你愿不愿意把一个平台特性真正当成“正经需求”去做而不是“兼容兼容就好”。我们最初也想过“鸿蒙后面再说”但实际上鸿蒙版本适配花了小一个月反而是整个项目里最有价值的一段工作。如果你也在做React Native的鸿蒙应用建议把鸿蒙调试环境、日志输出、原生桥接这些底子打好后面写业务代码会顺畅很多。
返回列表