先上一段 AI 给我生成的代码你就能懂我为什么说它红了一整页// ❌ DevEco Code 的 AI 自动补全出来的 CaseList 组件开头Componentexportstruct CaseList{Statecases:CaseItem[]CaseService.getHotList()// 编译报错Statekeyword:stringStatefiltered:CaseItem[]this.filterCases()// 编译报错还顺带逻辑错filterCases():CaseItem[]{returnthis.cases.filter(cc.title.includes(this.keyword))}build(){// ... 省略}}我盯着那两行红色波浪线愣了得有十秒。CaseService.getHotList()明明是个正常的异步方法为什么声明个变量都不让说白了ArkTS 的严格模式默认开有个挺反直觉的规矩成员变量的初始化表达式必须是编译期就能确定的纯值你不能在那儿调函数、不能写带副作用的逻辑、连this.xxx都不行。TS 里随手写private list load()没问题搬到 ArkTS 这儿编译器直接掀桌子报的就是arkts-no-side-effects-in-initialization。为什么鸿蒙要这么轴因为它想在编译期就锁死对象创建过程没有副作用这样运行时行为可预测也方便做并发和激进优化。代价就是我们写 TS 那套声明即计算的偷懒习惯得改。你猜怎么着我当时第一反应是这 AI 是不是抽风了还手动给getHotList()加了层括号想蒙混过关——没用报错换了个位置照样红。说起来雷达鸭鸿蒙版那个案例瀑布流最早就是让 DevEco Code 的 AI 生成的第一版就在声明处调了接口编译直接给我红了一页。坑一声明处调函数编译直接掀桌子根因上面说了。AI 按 TypeScript 肌肉记忆写初始化但 ArkTS 不吃这套。修复其实就一句话声明处只给字面量或空值真正的初始化逻辑挪到aboutToAppear里。// ✅ 初始化挪进 aboutToAppear声明处干干净净Componentexportstruct CaseList{Statecases:CaseItem[][]// 只给空数组Statekeyword:stringStateloading:booleantrueaboutToAppear():void{CaseService.getHotList().then((list:CaseItem[]){this.caseslistthis.loadingfalse}).catch((){this.loadingfalse})}build(){if(this.loading){Loading()return}List(){ForEach(this.cases,(item:CaseItem){CaseCard({item})},(item:CaseItem)item.id)}}}这个小改动能救你一晚上。我承认我一开始特别不习惯——TS 写久了谁没事把初始化都塞生命周期里——但 ArkTS 就这么定死的你拧不过它。提一句如果你非要在声明处给个算出来的值那只能是编译期常量或者字面量比如private pageSize: number 20这种调方法一律不行。还有个变体坑顺带说一下AI 有时候不死心把初始化塞进constructor()。ArkTS 的Component结构体压根没给你随便写构造函数去干这种活儿的口子正经路子就是aboutToAppear。我早期还真被它这么坑过一回编译倒是过了跑起来状态全空查了半天才发现赋值写进了根本不会被调用的地方。坑二派生状态用 State 存源变了它装死第一个坑好歹编译器会红脸提醒你。第二个坑才叫恶心它不报错、不白屏就是行为不对review 肉眼根本看不出来。还是那个列表页加了搜索框之后 AI 这么改的// ❌ 派生列表用 State 存源一变它不跟着变Componentexportstruct CaseList{Statecases:CaseItem[][]Statekeyword:stringStatefiltered:CaseItem[][]// 看着像状态其实是一次性快照aboutToAppear():void{CaseService.getHotList().then((list:CaseItem[]){this.caseslistthis.filteredlist// 只在初始化时算一次})}onSearch(value:string):void{this.keywordvalue// AI 忘了同步更新 filtered列表纹丝不动}build(){List(){ForEach(this.filtered,(item:CaseItem){CaseCard({item})},(item:CaseItem)item.id)}}}你搜咖啡keyword变了cases没变filtered更没变——因为它只在aboutToAppear里被赋值过一次从此变成一张静态快照。用户输入半天页面像死了一样。根子在于State只认自己那块内存的引用变化。你第一次this.filtered list换了引用它通知渲染之后你只动keyword和casesfiltered的引用纹丝没动框架自然觉得这哥们没改过我别重画。AI 的逻辑是初始化时算好就完事它压根没意识到派生数据是要跟着源活的。这种 bug 我搜了俩小时 Stack Overflow 都没找到对症的答案末了是自己打了日志才发现filtered压根没被重新算。正解有两种。最稳的、哪儿都能跑的是把派生数据写成只读 getter每次build自动重算根本不占State// ✅ 派生数据用 getter源 State 一变它自动重算Componentexportstruct CaseList{Statecases:CaseItem[][]Statekeyword:stringgetfiltered():CaseItem[]{returnthis.cases.filter((c:CaseItem)c.title.includes(this.keyword))}onSearch(value:string):void{this.keywordvalue// 只改源filtered 在 build 时自己重算}build(){List(){ForEach(this.filtered,(item:CaseItem){CaseCard({item})},(item:CaseItem)item.id)}}}要是你在 API 12 以上、想用状态管理 V2 那套更正式的机制可以把派生状态标成Computed它专门干这个活儿// ✅ API 12 状态管理 V2Computed 才是给派生状态准备的import{Computed}fromohos.arkui.StateManagementComponentV2struct CaseList{Localcases:CaseItem[][]Localkeyword:stringComputedgetfiltered():CaseItem[]{returnthis.cases.filter((c:CaseItem)c.title.includes(this.keyword))}build(){ForEach(this.filtered,(item:CaseItem){CaseCard({item})},(item:CaseItem)item.id)}}Computed的好处是它只在依赖的Local变了才重算比每次build都跑一遍 getter 省点开销。但我个人更常用上面那个 getter 写法——不是所有项目都升到了 V2老项目里ComponentV2和 V1 装饰器混用容易出更难查的坑。等一下这里我漏说一个前提Computed必须配合ComponentV2和Local用跟 V1 的State不是一套东西别在老组件里硬塞。收个尾我现在让 DevEco Code 的 AI 写 ArkTS 组件第一件事就是扫一眼变量声明处有没有偷偷调函数——只要看到 xxx()我就直接打回去重生成。这个习惯是拿两晚上的编译报错换来的。说实话我现在更信自己手写初始化AI 补全的组件我一律先过一遍声明处。你要是也常被这类能编译但行为诡异的坑咬建议把这条直接写进团队的 ArkTS 编码规范——别等上线了才发现有个列表死活搜不出来。你写 ArkTS 的时候AI 还给你埋过什么看起来能跑、实则埋雷的写法评论区聊聊我看看还有多少坑没踩完。我是老三10 年软件开发经验软件设计师、人工智能应用工程师。现在主要做鸿蒙应用开发ArkTS 北向和 Web 前端也在折腾 AI 自动化。偶尔在 CSDN 写点鸿蒙 / AI 方向的实战笔记。本文遵循 MIT 协议转载请注明出处。