最近在做一件事:把团队用 React Native 写的 Steam 资讯 App 往 OpenHarmony 设备上移植。UI 层其实没有想象中费劲,因为 RN 的社区适配已经能覆盖大部分基础组件,真正让人挠头的是系统能力——尤其是通知设置。通知设置页面表面上只是几个开关,背后却牵涉 React Native 与 OpenHarmony Notification Kit 的桥接、通知开关状态查询、用户偏好的持久化,还有和系统设置页的联动。这篇文章把我这个模块从建模到实现再到踩坑的完整过程写出来,做 React Native for OpenHarmony 的同学可以直接抄作业。
1. 项目背景:为什么是"Steam 资讯 + 通知设置"
1.1 资讯类 App 的通知业务模型
先交代一下场景。Steam 资讯 App 的典型业务是给用户推送游戏资讯、新品上架、折扣提醒、好友动态。这类 App 的通知有个特点:用户对内容的敏感度差异极大。有人只想收打折信息,有人只关心某几个游戏的资讯,还有人新游情报一条都不想错过。如果只做一个总开关,用户要么被信息轰炸到直接卸载,要么把所有通知全关掉,产品方完全失去触达渠道。
所以通知设置模块在设计上不能是"一个开关管全部",而是需要一套可组合的策略。我在项目里拆成了三层:
- 总开关:通知服务是否启用,对应系统层的通知权限
- 分类开关:资讯、折扣、好友动态、系统公告,每一类独立可控
- 细项配置:比如免打扰时段、声音是否跟随系统,这类属于偏好而非开关
这次实战文章主要讲总开关和分类开关的实现链路,免打扰时段属于本地业务逻辑,不是系统能力的关键路径。
1.2 React Native 跑在 OpenHarmony 上的现状
说实话,RN 生态往 OpenHarmony 上搬比想象中成熟。基础组件如 View、Text、FlatList、StyleSheet 基本能用,网络请求、图片加载也没遇到太大阻碍。原因在于 RN 本身是一层自绘 UI,OpenHarmony 的适配版把渲染层映射到了 ArkUI 的能力上,业务代码写起来和 iOS/Android 差异不大。
真正麻烦的是系统能力。通知、权限、震动、角标这类能力,RN 的 JS 侧没有现成的 API 可直接调用,你必须写桥接。桥接本身不难,难在文档少、样例少、报错信息不够友好。我这次做通知设置,百分之七十的时间都花在"桥接+踩坑"上,UI 反而半小时就写完了。
如果你的 App 属于"业务为主、系统能力为辅"的类型,用 RN for OpenHarmony 是划算的;如果 App 重度依赖系统能力,比如语音助手、后台保活、蓝牙外设,那你得先评估桥接工作量再决定。
1.3 本次模块的验收标准
技术文章最怕写完不知道成没成。我提前定了四条验收标准,后面所有测试都围绕这四条来:
- 首次安装启动,进入设置页时开关显示正确的默认状态
- 用户打开总开关,系统弹出通知授权确认,授权后开关保持打开
- 用户在系统设置里手动关闭通知后,回到 App 设置页,开关自动变成关闭态
- 在设置页能直接触发一条测试通知,验证整条链路通了
这样的验收标准直接对应了后续的所有实现和排查环节。
2. 环境准备与工程初始化:这一层的坑都在版本上
2.1 版本矩阵
做 OpenHarmony 开发,第一个要命的问题就是版本。我列一下这次实际用的版本组合,省得大家一个个去试:
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| DevEco Studio | 5.0.3 | 较新的版本对 API 12+ 支持更好 |
| OpenHarmony SDK | API 12 | Notification Kit 的接口最稳定 |
| Node.js | 18 LTS | 太新或太旧都和 RN CLI 有兼容问题 |
| react-native | 0.72.x | 社区适配版本基于这个分支维护 |
| react-native-openharmony | 跟随官方 release | 注意和 RN 版本配套 |
这里有个很容易踩的坑:DevEco Studio 新版本会自动下载最新的 SDK,但 OpenHarmony 的最新 SDK 不一定和 react-native-openharmony 的适配版本兼容。我一开始用的 API 13 预览版,结果 Notification Kit 某些接口签名变了,桥接层编译直接报错。最后老老实实退回 API 12,世界就清净了。
2.2 工程初始化
工程初始化分两步:
- 用 RN 的 CLI 创建 JS 侧工程,这套和做常规 RN 项目一样,创建完跑一遍确保 JS 代码能跑起来
- 在工程里加上 OpenHarmony 的 entry 模块,也就是 hap 壳工程
这里不推荐手工从零搭,直接参考 react-native-openharmony 官方 sample 的结构更省事。sample 工程里已经帮你把 entry、assets 加载、Metro 配置都串好了,你要做的是把自己的业务代码放进去,然后验证 Metro 是否能连上壳工程。
一个关键点是 bundle 资源路径。RN 开发时默认从 Metro 拉 JS bundle,但 OpenHarmony 真机上 Metro 需要走网络,调试时没问题,发布时必须把 bundle 打进 hap 包。我的做法是先在调试模式下跑通,再执行 bundle 打包命令,把产物放到 entry/src/main/resources/rawfile/index.ohos.bundle 下。
2.3 运行到真机
OpenHarmony 的开发调试通过 hdc 命令操作,类似 adb。安装命令是:
hdc install entry/build/xxx.hap调试时有一个细节:真机上的 RN App 要连到你 PC 上的 Metro,需要把 Metro server 的 host 设置成 PC 的局域网 IP,而不能是 localhost。否则真机加载不到 bundle,页面直接白屏。这个细节后面还会提到,它是启动白屏的常见原因之一。
3. 通知设置的数据层设计:先建模再写 UI
3.1 开关项与配置表
很多人写通知设置页喜欢直接在 JSX 里堆 Switch 组件,每行一个业务字段写死。这种写法后患无穷,因为通知类型以后一定会加,加一种就要改一遍页面。我习惯的做法是先定义一张配置表,页面遍历渲染。
我定义了一个开关项结构:
export type NotifySwitchKey = 'news' | 'discount' | 'friend' | 'bulletin'; export interface NotifySwitchItem { key: NotifySwitchKey; label: string; desc: string; defaultValue: boolean; } export const NOTIFY_SWITCH_CONFIG: NotifySwitchItem[] = [ { key: 'news', label: '游戏资讯', desc: '关注游戏的新闻动态推送', defaultValue: true, }, { key: 'discount', label: '折扣提醒', desc: '愿望单游戏降价时提醒你', defaultValue: true, }, { key: 'friend', label: '好友动态', desc: '好友上线或游戏成就动态', defaultValue: false, }, { key: 'bulletin', label: '系统公告', desc: '版本更新与产品公告', defaultValue: true, }, ];这样做的价值不只是少写几行代码,而是让新增通知类型变成"改配置"而不是"改页面逻辑"。社区经验是:通知设置的开关项一旦超过三个,配置化是必须的,否则下一个需求过来你就得在 UI 层挖坑。
3.2 状态管理:Zustand
状态管理我选了 Zustand。没选 Redux,因为这个页面的状态树很浅,只有几个布尔值,Redux 那一套在这种场景下明显偏重。Zustand 的写法直观,对 TypeScript 的类型推导也好。
通知设置 store 的骨架:
import { create } from 'zustand'; import { persist, createJSONStorage } from 'zustand/middleware'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { NOTIFY_SWITCH_CONFIG } from '../config/notifySwitches'; interface NotifyState { notifySwitches: Record<string, boolean>; setSwitch: (key: string, value: boolean) => void; resetAll: () => void; } const buildInitialSwitches = () => { const init: Record<string, boolean> = {}; NOTIFY_SWITCH_CONFIG.forEach((item) => { init[item.key] = item.defaultValue; }); return init; }; export const useNotifyStore = create<NotifyState>()( persist( (set) => ({ notifySwitches: buildInitialSwitches(), setSwitch: (key, value) => set((state) => ({ notifySwitches: { ...state.notifySwitches, [key]: value }, })), resetAll: () => set({ notifySwitches: buildInitialSwitches() }), }), { name: 'steam-news-notify-store', storage: createJSONStorage(() => AsyncStorage), } ) );persist 中间件会帮你把开关状态落到 AsyncStorage,下次启动还能恢复。这里要注意一个坑:不要用整个 store 里所有字段都持久化。如果以后 store 里加入 session 这类临时字段,会把脏数据也存进去。最好给 persist 配置 partialize 只筛选需要的字段。
3.3 默认值策略与首次启动
分类开关的默认值值得单独说。我调查过几个资讯类产品,常见做法分两种:
- 全部默认打开,用户自己关
- 按业务重要性分配,比如资讯和折扣默认开,好友动态默认关
第二种更合理。因为所有开关默认全开,对用户来说等于轰炸,很容易导致用户直接关掉总开关。而把低价值、高打扰的推送默认关掉,反而能给用户留出掌控感。
但无论哪种默认值,首次启动时都要注意一个点:用户还没有授权通知,此时页面显示的开关状态只是"本地偏好",不等于系统层的真实状态。展示层必须区分"偏好值"和"系统授权值"。这个区别后面写交互时要重点处理。
4. 桥接层:封装 OpenHarmony 通知能力
4.1 为什么必须写桥接
RN 的 JS 侧没有 OpenHarmony 的 notificationManager 模块,这是显然的。那些系统能力要么通过官方提供的 NativeModule 暴露,要么自己写桥接。通知相关的能力,官方适配版一般不会默认带上,所以基本靠自己。
桥接的边界很清楚:把"通知开关查询、请求开启、发通知"这三件事暴露给 JS。其余逻辑,比如是否弹引导、UI 展示、持久化,都留在 JS 层做。桥接层暴露的方法越少,后续维护越省心。
4.2 ArkTS 侧实现
先写一个纯粹的 ArkTS 服务类,封装 Notification Kit 的调用:
// entry/src/main/ets/service/NotificationService.ets import notificationManager from '@ohos.notificationManager'; export class NotificationService { static async isEnabled(): Promise<boolean> { return await notificationManager.isNotificationEnabled(); } static async requestEnable(): Promise<void> { await notificationManager.requestEnableNotification(); } static async publishNotification(title: string, text: string): Promise<void> { const request: notificationManager.NotificationRequest = { id: Date.now() % 100000, content: { notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT, normal: { title: title, text: text, }, }, }; await notificationManager.publish(request); } }这里的三个方法对应了系统层的核心语义:查询、请求授权、发布。重点解释一下第二个方法 requestEnableNotification:在 OpenHarmony 上,普通应用发布通知不需要申请运行时权限,但系统要求通知开关必须打开,这个方法会弹一个系统对话框,请求用户允许应用发送通知。等价于你在 iOS/Android 上第一次请求通知权限时的系统弹窗。
业务上即便你在 JS 侧把设置页的开关显示成打开,如果系统通知开关没打开,通知照样发不出去。所以"用户打开开关"这个动作必须触发 requestEnableNotification,让系统弹出授权框,拿到授权后通知链路才算真正打通。
4.3 JS 侧的 Promise 封装
在 JS 侧用 NativeModules 拿到桥接对象,再包一层错误处理。通知能力是异步的,而且失败场景很常见,不允许裸调用:
// src/services/notification.ts import { NativeModules } from 'react-native'; const { NotificationModule } = NativeModules; export const notifyBridge = { async isEnabled(): Promise<boolean> { try { return await NotificationModule.isEnabled(); } catch (e) { console.warn('[Notify] isEnabled error:', e); return false; } }, async requestEnable(): Promise<boolean> { try { await NotificationModule.requestEnable(); return true; } catch (e) { console.warn('[Notify] requestEnable error:', e); return false; } }, async publish(title: string, text: string): Promise<boolean> { try { await NotificationModule.publish(title, text); return true; } catch (e) { console.warn('[Notify] publish error:', e); return false; } }, };有一点要强调:桥接方法被拒绝时,catch 一定要有兜底返回值。通知设置页的 UI 交互依赖这些布尔返回值,如果异常时抛出来,页面可能崩,或者开关状态错乱。我的习惯是每个方法都返回布尔值或带错误结构的对象,绝不让异常穿透到 JS。
4.4 桥接层需要关注的时序问题
桥接层的实现不算难,但时序问题很容易被忽视。ArkTS 侧的方法执行完成后,通过 Promise 回调回到 JS 侧。RN 的 NativeModule 调用默认在 JS 线程执行,OpenHarmony 侧涉及通知服务的调用会跨进程,所以耗时不确定。
我遇到的一个典型问题是:用户在设置页快速连点开关,JS 侧并发发起了多次 requestEnable 调用,系统弹窗一个接一个弹出,体验极差。这里需要在 JS 侧做互斥,进入"请求中"状态后,Pending 的点击一律忽略。这个坑在后面的踩坑节里会详细展开。
5. 核心交互:从开关到通知发出的完整链路
5.1 开关与系统状态的联动逻辑
通知设置页的 UI 交互,不能简单理解成"Switch 绑一个布尔值然后 setState"。你需要一个状态机来处理"系统授权值"和"用户偏好值"之间的关系。
我在实现里把开关呈现分成两个层次:
- 总开关:直接绑定系统通知开关状态,来源是 notifyBridge.isEnabled()
- 分类开关:绑定 store 里的本地偏好,不需要询问系统
总开关的操作动作用于触发授权链路。点开总开关时,流程是:
- 先把 Switch 置为 pending/loading 状态,防止用户连点
- 调用 notifyBridge.isEnabled(),如果已经是 true,说明系统已授权,直接更新本地状态
- 如果是 false,调用 notifyBridge.requestEnable()
- 请求成功、系统中通知开关变成 true,再更新 UI 为打开状态
- 用户拒绝授权,UI 保持关闭,并且可以提示一句"通知关闭期间将无法收到任何推送"
这段逻辑如果写进 useCallback 里会比较长,我单独抽了一个函数:
const handleToggleMaster = useCallback(async (next: boolean) => { if (pendingRef.current) return; if (!next) { useNotifyStore.getState().setSwitch('master', false); return; } pendingRef.current = true; checkPanel.setLoading(true); const enabled = await notifyBridge.isEnabled(); if (enabled) { useNotifyStore.getState().setSwitch('master', true); } else { const ok = await notifyBridge.requestEnable(); if (ok) { useNotifyStore.getState().setSwitch('master', true); } else { useNotifyStore.getState().setSwitch('master', false); // show toast: 需要打开系统通知权限 } } pendingRef.current = false; checkPanel.setLoading(false); }, []);这里最关键的细节是:关闭总开关不需要请求系统,因为 OpenHarmony 没有提供代码直接关掉系统通知开关的能力。你能做的只是关闭本地偏好的"声音/震动"等子项,或者引导用户去系统设置里关。所以我们的关闭操作只是改 store。
5.2 引导用户去系统设置页
产品上有一个真实需求:用户在授权弹窗里点了拒绝,之后开关一直开不了。你总不能每次都弹系统授权框,OpenHarmony 对这种场景的约束是:已经拒绝过一次后,再次 requestEnableNotification 可能直接不弹窗。这时候就需要引导用户去系统设置页手动开启通知。
拉起系统设置页的方法是利用 want 启动系统的 settings 应用:
import common from '@ohos.app.ability.common'; import { Want } from '@ohos.app.ability.Want'; async function openSystemNotificationSettings(context: common.UIAbilityContext) { const want: Want = { bundleName: 'com.ohos.settings', abilityName: 'com.ohos.settings.MainAbility', }; try { await context.startAbility(want); } catch (e) { console.error('open settings failed', e); } }实际上不同 OpenHarmony 版本的 settings 应用包名和能力名可能不同,有的版本的通知设置页需要通过 uri 参数直达。最稳妥的做法是:先用上面这段拉起设置首页,再靠用户手动进通知页;如果想要直达,需要查目标设备上 settings 应用的声明配置。这个属于跟版本走的细节,我在公司里是专门写了一个 caniuse 表,记录每个版本的行为差异。
代码这边要注意 context 从哪里来。RN 工程里,壳工程的 MainAbility 入口有 UIAbilityContext,你可以在桥接的 ArkTS 侧把它保存为全局 context,或者每次调用时由 RN 的当前 UI 栈传入。我选择了在壳工程初始化时把 context 存到单例里,桥接方法直接拿。
5.3 通知渠道:Slot 配置
OpenHarmony 的通知服务引用了类似 Android 通知渠道的概念,叫 NotificationSlot。渠道的类型决定了通知的打扰级别、是否亮屏、是否震动等。如果不创建 slot 直接 publish,系统会按默认渠道处理,通常也弹得出来,但严重依赖系统默认行为,不可控。
我在获取授权后马上创建了两个 slot,一个用于一般资讯,一个用于重要提醒。Slot 的创建在 ArkTS 侧:
import notificationManager from '@ohos.notificationManager'; async function setupNotificationSlots() { const slotInfo = { type: notificationManager.SlotType.SOCIAL_COMMUNICATION, level: notificationManager.SlotLevel.LEVEL_HIGH, }; await notificationManager.addSlot(slotInfo); }这里 SlotType 的选择不要乱用。资讯推送用 CONTENT_INFORMATION 更合理,好友动态才用 SOCIAL_COMMUNICATION。Slot 级别如果设成 LEVEL_DEFAULT,通知会出现在列表但可能不打扰;设成 LEVEL_HIGH 则会有横幅和声音。我的建议是:重要通知用高等级,普通推送用默认,宁可少用打扰。
5.4 发布一条测试通知
设置页里放一个"发送测试通知"按钮,这个按钮的调试价值极大。点一下走了完整的 publish 链路,能直接验证桥接、slot、系统通知开关是否都正常。
测试通知的调用很简单:
const ok = await notifyBridge.publish('Steam 资讯', '这是一条测试通知,如果你看到了说明通知链路正常');这个按钮我强烈建议保留到正式环境,藏在设置页底部或开发者菜单里。它能在现场排查问题时节省大量时间。有一次用户反馈收不到通知,我让他在设置页点测试通知,发现本地通知能发出来,只有服务端推送收不到,瞬间就把问题定位到了推送服务侧,而不是客户端。
6. 踩坑记录:白屏、状态不同步和通知不弹
6.1 启动白屏的排查链路
React Native 在 OpenHarmony 上启动白屏,这是社区里被问烂的问题。我第一次遇到时排查了很久,后来总结出一条排查链路,按这个顺序查,五分钟内基本能定位。
第一步,看 Metro 日志。如果 Metro 没有输出 bundle 请求记录,说明 App 压根没去找 Metro。这通常是壳工程的 bundleUrl 配错了,指向了不存在的地址。
第二步,看 assets 里有没有 bundle 文件。发布模式下 RN 跑的是打进 hap 的 bundle。路径在 entry/src/main/resources/rawfile/ 下,文件名要和壳工程加载代码里写的一致。举个例子,如果加载代码里写的是index.ohos.bundle,但产物叫index.android.bundle,那白屏就是必然的。
第三步,检查 View 挂载时机。react-native-openharmony 的壳工程会在 Ability 的 onWindowStageCreate 里做初始化。有时你把 RN 的初始化放到了 onForeground,窗口还没就绪,渲染出来的就是空白。
还有一种隐蔽情况是 Metro 端口被占用。RN 默认是 8081,如果本机另一个进程占了这个端口,Metro 会换端口,但壳工程里的配置没跟着变,白屏。排查方法很简单,Metro 起来后看终端第一行输出的 URL,对比壳工程里的加载地址。
6.2 首次启动开关状态闪烁
设置页打开时,总开关的值来自系统查询,而系统查询是异步的。如果你初始渲染时给 Switch 一个默认值比如 true,等异步查询返回 false,用户会看到开关闪一下从开变关,体验很差。
我的解决方案是给总开关加一个"加载中"状态。在通知开关的查询结果返回前,Switch 保持 disable 或显示 loading,查询完成后再渲染真实值。这个思路说起来简单,但很多人漏掉,是因为只在 Android 上做通知设置时,系统的 query 非常快,几乎感知不到异步。OpenHarmony 上的适配层查询明显慢,闪烁问题就暴露了。跨端开发就是这样,稍慢的环节就要考虑 loading 态。
6.3 通知授权弹窗与页面生命周期冲突
这个坑值得单独记录。我在测试时发现一个现象:从设置页点击"开启通知",系统授权弹窗弹出来,但没等用户点击,页面就切到了后台,等用户再回来时授权结果丢失,开关状态错乱了。
根因是 requestEnableNotification 的 Promise 在 UIAbility 进入后台时被系统挂起,返回时机不可控。解决思路是不要把授权结果绑定到页面的局部状态,而是统一写入 store,并且进入前台时重新查询系统状态。
我在壳工程的 onForeground 回调里加了一个事件通知,当页面重新回到前台,设置页重新调用 isEnabled() 刷新总开关。这样即使授权结果丢了,用户回来时也会拿到最新的真实状态。
6.4 通知渠道导致的"不弹通知"
还有一次测试通知发不出去,查了很久发现是 Slot 没创建成功。原因是 addSlot 方法需要在通知服务初始化之后调用,我在 App 启动时太早调用了 addSlot,系统还没就绪,返回失败被打进了 catch 里,后续 publish 也没有报错,但通知就是不见踪影。
所以 addSlot 和 publish 之间要有依赖关系,publish 之前建议先检查当前 slot 是否有值。我在桥接层里加了一个 ensureSlot 方法,publish 时如果发现 slot 不存在,先补创建再发布。另外,同一个类型的 slot 重复创建不会报错,但如果你修改了 level 参数,需要先 removeSlot 再 addSlot,否则新的 level 不生效,这个容易让人误以为"代码没生效"。
6.5 系统设置改开关之后,App 内不同步
用户到系统设置页把通知关掉,再切回 App,设置页的总开关还显示打开状态。这在原生开发里也要处理,很多新手会忽略。RN 开发时我采用两个机制:
- AppState 监听,从 background 回到 active 时重新查询 isEnabled
- 设置页的 onFocus 回调里重新查询
实现了一个轻量的 hook 来做这件事:
function useForegroundRefresh(refreshFn: () => void) { const appState = useRef(AppState.currentState); useEffect(() => { const sub = AppState.addEventListener('change', (nextState) => { if (appState.current === 'background' && nextState === 'active') { refreshFn(); } appState.current = nextState; }); return () => sub.remove(); }, [refreshFn]); }这个 hook 不只用于通知设置页,后续任何需要"回到前台刷新"的页面都能复用。和页面生命周期绑在一起的逻辑抽成 hook,是跨端开发里避免重复代码的有效手段。
7. 验收清单与可扩展方向
7.1 手动验收清单
写完了实现,最后做一个手动验收。我把测试步骤做成清单,方便团队其他成员自测:
| 测试场景 | 操作方式 | 预期结果 |
|---|---|---|
| 首次启动 | 安装后直接进设置页 | 总开关为关或按产品策略显示引导,分类开关显示默认值,开关不闪烁 |
| 打开总开关 | 点击总开关 | 系统弹出授权框,同意后开关保持打开 |
| 拒绝授权 | 点击总开关,在弹出的系统框中拒绝 | 开关回到关闭,提示引导文案 |
| 系统设置关闭 | 到系统设置里关闭通知,回 App | App 设置页总开关自动变为关闭 |
| 发送测试通知 | 点击测试通知按钮 | 通知栏出现一条普通文本通知 |
| 分类开关持久化 | 关闭折扣提醒,杀死 App 重启 | 折扣提醒保持关闭,其他开关不受影响 |
这个清单也适合放到 CI 里做冒烟测试的人工入口,我后来把测试通知按钮做进了开发菜单,QA 每次提测前会先跑一遍。
7.2 后续扩展:从本地通知到服务端推送
这篇文章的核心是本地通知闭环,但对资讯类 App 来说,最终形态一定是服务端推送。OpenHarmony 上的服务端推送属于独立的推送服务,和本地通知走的不是一条链路。
不过有一点可以提前规划:页面 UI、开关配置、偏好持久化这些都不需要改,只需要新增一个桥接方法用来注册推送 token,以及处理服务端消息到达后的展示。也就是说,本文的设置在架构上能平滑过渡到推送服务,这是我在设计桥接层时特意预留的扩展点。
富文本通知也是后续可以考虑的方向。OpenHarmony 的 NotificationRequest 里除了 BASIC_TEXT,还有很多内容类型,比如长文本、图片、进度条。如果你做的是游戏资讯类,封面图通知的点击率通常明显高于纯文本。实现上是在桥接层增加一个新的 publish 方法,接收 imageUri 等参数,JS 侧包一层即可。
7.3 一点个人体会
做了几轮跨端适配,给准备入坑的同学一个最直接的建议:桥接层一定要做薄,只放系统能力,所有业务逻辑不要进 ArkTS。通知设置这个模块,ArkTS 侧只做了三件事——查开关、请求授权、发通知,其余状态管理、策略判断、引导逻辑全在 JS 侧。这么做的好处是,就算以后整个壳工程换掉,业务代码也能完整迁移。
还有就是在开发前期就把各种异常返回路径想全。通知设置是最容易出"看似没反应"问题的模块,授权被拒、系统开关被外部改变、时序错乱,这些都属于常态而不属于意外。我在做这个模块的时候,把能 catch 的异常都 catch 住并返回兜底值,后来线上反馈反而少了很多。面对系统能力,防御式编程不是保守,是务实。