环境:DevEco Studio + HarmonyOS SDK(targetSdkVersion 26 / compatibleSdkVersion 5.0.3(15)),Stage 模型,ArkTS + ArkUI 声明式写法。 网络库:
@ohos/axios(社区维护的 ArkTS 版 axios,API 与 axios 基本一致)。 后端:Express + MySQL,约定统一返回{ code, msg, data }。 本文所有代码均来自我实际跑通的工程,IP 等环境信息已做脱敏处理。
用 ArkUI 写页面时,只要后端是 RESTful 风格,就一定会碰到这几个问题:
baseURL,改一次地址要改十处;Authorization 头,漏一个就 401;{ code, msg, data },但 axios 只会把 data 当成功,业务码得自己判断,容易漏;所以这篇的核心目标只有一个:把"请求层"抽出来,让页面只关心业务。
ArkTS 是强类型语言,把接口出入参先定义清楚,后面所有页面的提示都能自动补全,也能避免 any 满天飞。
model/LoginType.ets:
model/CityListType.ets:
命名上我用的是「请求类型 / 响应类型 / 响应数据类型」三级结构:*Request* 是入参,*Response* 是含 code/msg/data 的完整响应,*ResponseData* 是 data 内部真正的业务数据。这样在页面上 response.data.data 层层递进虽然多打几个字,但类型完全对得上。
新建 utils/MyAxios.ets:
三个要点:
axios.create() 返回实例,业务代码全部用 instance.get/post,不再直接 axios.get,地址只在这一处配置;Authorization: Bearer <token>,token 从 AppStorage 里取,页面完全不用关心鉴权头;AppStorage 是 HarmonyOS 的全局状态容器,AppStorage.set 写入后全应用任意页面都能读到,比自己造单例更贴合 ArkUI 的状态模型。pages/Index.ets:
@StorageLink vs @StorageProp 是这里最容易被忽略但很关键的一对:
装饰器 | 与 AppStorage 的关系 | 用在哪 |
|---|---|---|
@StorageLink | 双向绑定,改页面值会写回 AppStorage,AppStorage 变了页面也会刷新 | 登录页(写入 token) |
@StorageProp | 单向同步,AppStorage 变了页面会刷新,但改页面值不会回写 | 列表页(只读用户名) |
写反了就会出现"登录页写入的 token 列表页读不到"这类玄学问题。
pages/CityList.ets:
注意 instance.get('/api/cities') 这一行——代码里完全看不到 token,但请求头里一定有 Authorization,因为拦截器已经包掉了。这就是抽象请求层的价值:鉴权逻辑只写一次,以后加签名字段、加密、埋点都只改一个文件。
新增城市 pages/CityAdd.ets:
后端返回的 update_time 是 2026-10-14T08:24:16.000Z 这种 ISO 串,直接显示很难看,我写了个格式化工具 utils/artTs.ets:
用正则只取日期部分,同时兼容 2026-10-14、2026/10/14、2026.10.14 三种分隔符,非法日期直接返回空串而不是崩掉。
我最初写的是:
运行时直接报路由找不到。ArkUI 的路由 url 必须和 main_pages.json 里登记的路径完全一致,而登记的是 pages/CityList(目录名是 pages,不是 page)。改回来就好了。
main_pages.json 是路由白名单,所有用 router.pushUrl 跳转的页面都必须在这里出现:
我另一个工程里写了 router.pushUrl({url:"pages/UserRegister"}),但 main_pages.json 里只有 "pages/Index",跳转直接失败。新建页面后第一件事:去 main_pages.json 登记。
AppStorage.get 可能拿到 undefined拦截器这段在用户还没登录时也会执行:
如果此时 AppStorage 里根本没有 userInfo 这个键,AppStorage.get 返回 undefined,强制类型断言不会改变运行时行为,紧接着取 .token 就会抛 Cannot read property 'token' of undefined,整个请求直接崩在拦截器里。
修法是加一层空值保护:
我一开始写 timeout: 500(毫秒),走真实网络时后端稍慢就超时。后调成 timeout: 2000 才稳定。内网自测不代表生产表现,超时值要按最慢接口来定。
现在的 MyAxios.ets 只处理了"发请求时加 token",但没处理"收响应时 token 过期"。后端返回 401 时,页面只会走进 .catch,用户看到的是一句无意义的报错。
建议补一个响应拦截器,统一处理鉴权失效:
<响应数据类型, AxiosResponse<响应数据类型>, 请求数据类型>,写错位置 TS/ArkTS 立刻报类型不匹配。GET 没有请求体,第三参数传 null。baseURL 要不要带 /api 需和请求路径对应。我另一个工程 baseURL 里带了 /api,请求路径就不能再写 /api/xxx,否则会拼成 /api/api/xxx。any 和动态属性,接口返回的动态字段要显式声明 interface,否则 .ets 编译直接失败。@State 存接口数据、@StorageLink/@StorageProp 存全局状态,两者混用容易出现"数据改了视图不刷新"。整套流程跑通后,页面上写请求就是一行:
地址统一、鉴权统一、类型统一、错误处理统一,后续加新接口基本是复制粘贴。后续我打算在这套请求层之上,用 startAbilityForResult 把登录独立成单独的 Ability,让登录页用 terminateSelfWithResult 把 token 回传给主 Ability,替代现在的全局 AppStorage 传递方式——这也是我下一步要啃的 Ability Kit 实践。
本文代码均为实际工程实现,环境信息已脱敏。欢迎交流指正。 #WorkBuddy# #HarmonyOS# #ArkTS# #ohos-axios
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。