02 ArkTS 语言与工程规范
一、详细知识点1. 类型系统ArkTS 开发要尽量避免“随便传对象”。页面、接口、缓存和组件都应该有明确类型。exportinterfaceNewsArticle{id:stringtitle:stringsummary:stringcontent:stringcategory:stringpublishTime:stringreadMinutes:numberfavorite:boolean}工程要求API DTO 与页面模型分离。可空字段必须显式处理。不让页面直接依赖后端原始字段。2. 接口、类与枚举接口适合表达数据结构类适合表达默认值和行为枚举或字面量联合类型适合表达有限状态。exporttypePageNamehome|detail|favorites|settingsexportclassUserSetting{darkMode:booleanfalsefontScale:number1}3. 模块化import{NewsArticle}from../model/NewsArticleimport{NewsService}from../services/NewsService规范model只放类型和轻量模型。services放业务动作。pages不直接写复杂数据转换。components尽量可复用依靠入参和回调。4. 异步与错误边界privateasyncreload():Promisevoid{this.loadingtruethis.errorMessagetry{this.articlesawaitNewsService.queryArticles()}catch(error){this.errorMessage加载失败请重试}finally{this.loadingfalse}}工程要求所有远程、文件、权限和系统能力调用都必须有失败路径。错误要包含业务上下文但不能泄露隐私。页面要有加载中、空状态、失败状态和重试入口。5. 工程编码规范规则原因示例类型明确降低运行时错误let articles: NewsArticle[] []函数单一职责便于测试toggleFavorite(id)页面薄、服务厚降低 UI 耦合Page 调用 Service状态可追踪避免刷新混乱页面状态集中定义错误可恢复用户体验完整失败提示 重试二、本章 demoDemo 1数据模型见demo/harmony-news-demo/entry/src/main/ets/model/NewsArticle.etsexportinterfaceNewsArticle{id:stringtitle:stringsummary:stringcontent:stringcategory:stringpublishTime:stringreadMinutes:numberfavorite:boolean}Demo 2服务层模拟异步请求见services/NewsService.etsstaticasyncqueryArticles():PromiseNewsArticle[]{awaitdelay(250)returnMOCK_ARTICLES.map((article)({...article}))}Demo 3浏览器预览同源逻辑demo/web-preview/app.js使用同样的文章模型、收藏状态和设置状态保证没有 DevEco 命令行时也能验证业务流程。三、面试题与详细答案1. ArkTS 项目为什么要重视类型类型是鸿蒙工程可维护性的基础。页面、组件、服务和缓存之间如果没有明确类型字段变更会很难定位运行时错误也会增多。明确类型还能让 IDE 自动补全、编译检查和重构更可靠。追问接口返回字段和页面模型是否应该完全一致不一定。生产项目建议 DTO 与页面模型分离中间用转换函数处理默认值、空值和字段命名。2. 页面层为什么不建议直接处理复杂业务页面层主要负责展示和交互。如果网络请求、缓存、数据转换、权限处理都写在页面里会导致页面难测试、难复用、难排错。更好的方式是 Page 调用 ServiceService 调用 Store 或 Remote API。3.async/await的错误应该在哪里处理Service 层负责记录技术错误和转换业务错误Page 层负责展示用户可理解的状态。不能只在底层catch后返回空数据否则页面无法区分“真的为空”和“加载失败”。4. 如何判断一个组件是否拆得合理看三个指标是否能复用、是否降低父页面复杂度、是否有清晰输入输出。比如NewsCard接收article和事件回调自己不关心数据从哪里来这就是合理拆分。四、五倍扩展知识点矩阵1. ArkTS 能力地图能力基础要求工程化要求工程要求基础类型会用 string/number/boolean能处理联合类型和数组能设计稳定领域模型接口会定义字段区分 DTO 和 ViewModel能做版本兼容转换类会写默认值封装行为避免把类变成全局状态容器模块会 import/export按目录拆分控制依赖方向异步会 async/await处理 loading/error设计取消、重试、超时集合会数组 map/filter/find不直接修改被追踪对象控制大数据性能空值会判断 undefined路由参数校验系统性设计边界异常会 try/catch区分技术错误和业务错误错误可观测、可恢复泛型理解通用结构封装 Result/Repository保持类型安全和可读性代码规范命名清晰分层清晰可测试、可审查、可演进2. 类型设计扩展exportinterfaceApiResponseT{code:numbermessage:stringdata?:T}exportinterfaceNewsDto{article_id:stringheadline:stringdigest?:string}exportinterfaceNewsArticle{id:stringtitle:stringsummary:string}exportfunctiontoArticle(dto:NewsDto):NewsArticle{return{id:dto.article_id,title:dto.headline,summary:dto.digest??暂无摘要}}为什么要做转换后端字段可能用下划线页面模型可能用驼峰后端字段可能为空页面需要默认值后端返回可能多字段页面只需要一部分。转换层能保护 UI 不被接口变化直接冲击。3. Result 模式exporttypeResultT|{success:true;data:T}|{success:false;message:string;retryable:boolean}asyncfunctionloadNews():PromiseResultNewsArticle[]{try{constdataawaitNewsService.queryArticles()return{success:true,data}}catch(error){return{success:false,message:新闻加载失败,retryable:true}}}Result 模式的价值是让调用方明确处理成功和失败而不是把异常藏在空数组里。可上线代码不把“没有数据”和“加载失败”混为一谈。4. 异步并发与顺序场景推荐写法原因必须按顺序await step1(); await step2()保证依赖顺序可并发Promise.all缩短等待时间任一成功即可Promise.race或业务封装做降级可重试请求包装 retry 函数避免页面重复写逻辑页面退出记录请求版本避免旧请求覆盖新状态页面常见问题用户快速切换页面旧请求回来后覆盖新页面状态。解决方式是维护请求序号只接受最后一次请求结果。5. 模块依赖规则pages - components pages - services services - model services - storage/network components - model model - no dependency不要让model反向依赖pages不要让components直接调用网络服务不要让services引用具体 UI 组件。依赖方向越稳定项目越容易维护。五、ArkTS demo 扩展任务任务文件目标验证增加NewsCategory类型model限制分类取值错误分类编译失败增加 DTO 转换services模拟接口转换页面字段不变增加 Result 返回NewsService区分失败和空列表页面显示错误增加请求版本号Index.ets防旧请求覆盖快速刷新不乱增加搜索函数NewsService练习 filter搜索标题成功增加排序函数NewsService练习不可变数组阅读时长排序增加设置模型校验SettingsStore限制字体范围不能超出区间增加收藏统计FavoriteStore练习派生数据显示收藏数量增加错误日志上下文services便于排查日志含方法名增加单测伪代码docs建立测试意识每个函数有输入输出扩展示例请求版本号privaterequestVersion:number0privateasyncreload():Promisevoid{constcurrentVersionthis.requestVersionthis.loadingtrueconstarticlesawaitNewsService.queryArticles()if(currentVersion!this.requestVersion){return}this.articlesarticlesthis.loadingfalse}这个写法解决“连续点击刷新后返回的旧请求覆盖新请求”的问题。生产项目还会配合取消请求、超时和重试。六、常见编码错误错误后果修复任意对象到处传字段变更难定位定义接口和转换函数页面直接写 mock 数据页面不可复用移到 Servicecatch 后返回空数组无法区分失败和无数据使用 Result 或错误状态深层修改对象UI 可能不刷新生成新对象或新数组命名过短可读性差使用业务语义命名Service 引用页面依赖倒置Service 只处理业务Store 无限增长内存风险做容量和清理策略日志输出敏感信息安全风险脱敏和分级异步结果不校验状态错乱请求版本或取消机制类型全写 any编译保护失效用接口、泛型、联合类型七、扩展面试题5. DTO、Entity、ViewModel 有什么区别DTO 面向接口传输字段由后端或协议决定Entity 或领域模型面向业务含义ViewModel 面向页面展示。三者分离可以降低接口变化对 UI 的影响。小 demo 可以合并但生产项目建议至少区分接口返回和页面模型。6. 为什么不建议滥用全局单例 Store单例 Store 简单但容易带来隐式依赖、测试困难、生命周期不清和内存长期占用。适合保存轻量、明确、跨页面共享的数据复杂业务要结合模块边界、持久化和清理策略。7. 如何设计一个可测试的 ServiceService 应该输入明确、输出明确不直接依赖 UI不直接弹窗不读取隐式页面状态。网络和存储可以通过接口抽象或可替换函数注入。这样单元测试可以用 mock 数据验证成功、失败和边界。8.try/catch为什么不能随便吞错误吞错误会让上层无法判断失败原因用户也看不到正确状态。正确方式是记录必要上下文把底层错误转换成业务可理解的错误再由页面展示重试、空状态或降级。9. 什么时候需要泛型当多个结构只有数据类型不同而处理逻辑一致时例如ApiResponseT、ResultT、PageDataT。泛型可以减少重复但不要为了炫技过度抽象影响团队理解。10. 如何避免 ArkTS 工程越写越乱先定目录职责再定依赖方向再定命名和错误处理规则。每新增一个功能都要问类型放哪里、服务放哪里、状态属于谁、失败怎么展示、是否需要测试。长期坚持这些问题工程复杂度才不会失控。八、ArkTS 知识点详解库1. 类型是架构边界类型不仅是编译工具提示它定义了模块之间的契约。NewsArticle一旦成为页面模型组件、服务和测试都会依赖它。随意改字段会影响所有调用方因此字段命名、可空性和默认值都要谨慎。2. 可空字段必须有业务解释字段可空不是简单加?。要说明为什么可空接口可能缺失、权限未授权、用户未填写还是缓存版本旧。不同原因对应不同 UI 和错误处理。3. 接口转换是防腐层接口字段属于外部系统页面模型属于本应用。中间转换层可以防止后端字段命名、默认值、枚举变化直接影响 UI。可上线项目会把转换函数单独测试。4. 不可变更新更适合声明式 UI在声明式 UI 中生成新数组和新对象通常比深层原地修改更安全。它让状态变化更明确也更容易触发刷新和调试。5. 异步函数要有完整状态一次异步加载至少对应四种状态未开始、加载中、成功、失败。很多页面只写成功状态导致失败时用户看到空白。6. 错误要分类网络错误、权限错误、数据格式错误、业务错误、未知错误应分开处理。分类后才能决定是否重试、是否提示用户、是否上报日志。7. 业务函数不要返回魔法值例如用空字符串表示失败、用-1表示不存在会让调用方猜语义。更好的方式是返回undefined、ResultT或明确错误对象。8. 命名要表达业务handleClick、changeData、doSomething不能表达意图。toggleFavorite、queryArticles、updateFontScale能直接说明业务行为。9. 模块越底层越不能依赖 UImodel和services应该不知道页面怎么展示。底层依赖 UI 会导致业务逻辑无法复用也难以测试。10. Store 需要生命周期策略内存 Store 简单但应用重启会丢失持久化 Store 稳定但要处理版本迁移、损坏数据和清理。选择 Store 要基于业务数据生命周期。11. 泛型要服务真实重复只有当多个函数结构相同、类型不同才需要泛型。过度泛型会降低可读性让普通业务开发者难以维护。12. 代码审查要看边界ArkTS 代码审查不只看语法还要看类型是否清晰、异常是否处理、状态是否可追踪、模块依赖是否合理、日志是否安全。九、ArkTS 场景化实战库场景练习点实现方向验收标准新闻搜索字符串过滤filter标题和摘要输入后列表变化分类过滤联合类型限制分类值分类不乱写阅读排序数组复制排序不改原数组排序可恢复收藏切换Store增删 id计数正确设置校验数值边界限制 0.85-1.25超界无效接口转换DTO Mapper下划线转驼峰页面字段稳定错误显示Result成功失败分支失败可重试请求防抖异步控制延迟执行连续输入不卡请求去重缓存 Promise同请求复用不重复加载旧请求丢弃版本号只接受最新快速刷新不乱日志脱敏字符串处理隐藏敏感值日志安全单元测试纯函数输入输出断言可自动验证十、扩展面试题库11. ArkTS 中为什么要避免大量anyany会绕过类型检查让编译器失去保护能力。短期写起来快长期会让字段错误、空值错误和接口变化更晚暴露。生产项目应使用接口、联合类型、泛型或明确转换函数。12. 如何处理接口字段新增和删除新增字段通常不会影响旧页面但删除或改名会破坏转换层。应把接口 DTO 和页面模型分开转换函数中处理默认值和兼容逻辑并给关键转换补测试。13. 为什么 Service 不应该直接弹 UI 提示Service 是业务层不应该知道页面展示方式。直接弹提示会让 Service 难测试、难复用。正确方式是返回业务结果由 Page 决定展示 Toast、空状态、弹窗还是重试。14. 如何设计错误对象错误对象至少包含用户文案、技术原因、是否可重试和日志上下文。用户文案给页面展示技术原因给日志是否可重试决定按钮日志上下文帮助定位。15. 什么时候应该抽公共工具函数当同一逻辑出现三次以上或者逻辑本身有明确业务含义且需要测试时可以抽。不要把只有一行且语义不稳定的代码过早抽象。十一、ArkTS 语言体系补全主题必须掌握项目中怎么用常见错误基础类型string、number、boolean、array、object定义页面状态和模型用any绕过检查字面量类型固定状态集合type PageName ‘home’‘detail’interface数据契约DTO、ViewModel、配置对象字段可空性不清class默认值和行为UserSetting、错误模型把 class 当全局变量enum/联合类型有限状态分类、页面名、错误码状态值散落泛型通用响应结构ResultT、PageDataT抽象过度模块import/exportmodel/service/component 分层循环依赖异步Promise、async/await网络、文件、权限不处理失败并发Promise.all、请求版本并行加载、丢弃旧请求旧结果覆盖新状态异常try/catch、Result错误映射catch 后静默集合map/filter/find/reduce列表、搜索、统计原地修改导致状态不清函数式转换DTO - Model接口防腐层页面直接吃接口字段十二、ArkTS 工程模板1. 统一结果模型exporttypeAppErrorCode|NETWORK_TIMEOUT|PERMISSION_DENIED|DATA_EMPTY|DATA_INVALID|UNKNOWNexportinterfaceAppError{code:AppErrorCode message:stringretryable:booleancause?:string}exporttypeAppResultT|{ok:true;data:T}|{ok:false;error:AppError}这样页面能明确知道是否成功、失败原因、是否可重试、应该显示什么文案。2. DTO 转换模板interfaceNewsDto{id?:stringtitle?:stringdigest?:stringcontent?:stringtag?:string}functionnormalizeNews(dto:NewsDto):NewsArticle{return{id:dto.id??local-${Date.now()},title:dto.title??未命名内容,summary:dto.digest??暂无摘要,content:dto.content??,category:dto.tag??General,publishTime:newDate().toISOString().slice(0,10),readMinutes:Math.max(1,Math.ceil((dto.content?.length??100)/300)),favorite:false}}3. 请求去重模板classRequestCacheT{privatepending?:PromiseTrun(task:()PromiseT):PromiseT{if(!this.pending){this.pendingtask().finally((){this.pendingundefined})}returnthis.pending}}用于首页重复刷新、配置重复拉取、用户信息重复请求。4. 状态机模板typeLoadStateT|{type:idle}|{type:loading;keepOldData:boolean}|{type:success;data:T}|{type:empty;message:string}|{type:error;message:string;retryable:boolean}页面不要只用loading: boolean和errorMessage: string凑合。复杂页面用状态机更清晰。十三、编码规范补强规则原因检查方式页面不直接写接口 DTO防止接口变化冲击 UI看 Page 是否 import DTOService 不引用 UI 组件保持业务层可测试看 services 是否 import pages/componentsStore 不无限保存数据防止内存和隐私问题看是否有清理策略日志不包含敏感字段防止隐私泄露搜索 token、phone、location错误不静默用户可恢复catch 是否返回明确结果异步有并发策略防止状态乱序是否有请求版本/去重/取消数组更新生成新对象响应式更稳定是否大量原地修改命名带业务含义提升可读性禁止doIt、data1十四、ArkTS 练习套件练习输入输出要求DTO 转换不完整新闻对象完整NewsArticle默认值合理搜索过滤keyword list过滤列表大小写和空值处理分类过滤category list分类列表未知分类返回空收藏切换id favorites新 favorites不原地污染错误映射unknown errorAppError文案可展示请求版本连续请求最新结果旧请求不覆盖分页合并old pagemerged去重设置校验scalesafe scale范围控制缓存解析JSON stringmodel/null损坏数据不崩统计派生listcount/map不重复计算十五、补充面试题16. ArkTS 中如何设计页面状态页面状态要能表达完整 UI未加载、加载中、成功、空数据、失败、提交中。简单页面可用多个State复杂页面建议使用状态机结构避免 boolean 组合出不可能状态。17. 为什么 DTO 转换函数值得单独测试DTO 是外部输入最容易出现字段缺失、类型变化和版本兼容问题。转换函数如果稳定页面模型就稳定。测试转换函数能提前发现接口变更影响。18. 如何防止旧异步请求覆盖新状态可以维护请求版本号、使用取消机制或请求去重。每次发起请求记录当前版本结果回来时只接受最新版本旧结果直接丢弃。19. ArkTS 模块拆分的核心原则是什么按职责和依赖方向拆分。模型不依赖服务服务不依赖 UI组件不直接操作系统能力。依赖方向稳定项目才能长期维护。20. 什么时候使用状态机而不是多个布尔值当页面有多种互斥状态时使用状态机。例如 loading、empty、error、success 不能随意组合。状态机能让 UI 分支更清楚也便于测试。