news 2026/7/19 15:28:32

HarmonyOS ArkTS 实战重构:从单文件 到模块化拆分的完整过程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HarmonyOS ArkTS 实战重构:从单文件 到模块化拆分的完整过程

引子:代码写完能跑就行?没那么简单

星空运势应用第一版写完的时候,所有代码塞在一个Index.ets里,500 多行,三个页面、数据定义、状态管理全搅在一起。功能没问题,预览效果也挺好,但每次想改点什么都要在文件里翻半天。

完整效果

重构前的问题

先看看重构前的 Index.ets 长什么样:

Index.ets(500+ 行) ├── 数据定义(Pos2D, StarPt, SignData, TarotInfo, SIGNS, TAROT, WHEEL_COLORS...) ├── @State 状态(page, wheelAngle, spinning, selectedSign, dealt, tarotIdx, tarotOpen) ├── @Builder TopNav() ├── @Builder WheelPage() ├── @Builder StarPage() ├── @Builder TarotPage() ├── @Builder StackedDeck() ├── @Builder DealtCards() ├── @Builder Readings() ├── @Builder StarDetail() └── build() 主入口

主要问题

  1. 数据和 UI 混在一起:星座数据、塔罗数据、颜色配置全写在组件上面,和 UI 代码混在一起
  2. 状态耦合:7 个@State全在同一个组件里,改转盘可能影响星图
  3. Builder 过多:8 个@Builder全塞在 Index 里,找代码要翻来翻去
  4. 复用困难:转盘、星图、塔罗的逻辑无法单独复用

重构后的目录结构

重构后的项目结构变成了这样:

entry/src/main/ets/ ├── pages/ │ └── Index.ets # 主入口(精简到 ~100 行) ├── view/ │ ├── WheelView.ets # 转盘页面组件 │ ├── StarView.ets # 星图页面组件 │ └── TarotView.ets # 塔罗页面组件 ├── model/ │ ├── Types.ts # 数据类定义(Pos2D, StarPt, SignData, TarotInfo) │ ├── SignsData.ts # 12 星座完整数据 │ ├── TarotData.ts # 21 张塔罗牌数据 │ └── Constants.ts # 颜色、运势标签等常量 ├── entryability/ │ └── EntryAbility.ets └── resources/

核心变化

  • Index.ets 从 500+ 行精简到 ~100 行
  • 三个页面各自独立成@Component
  • 数据定义全部移到model/文件夹
  • 每个文件职责单一,改一个不影响另一个

拆分过程详解

第一步:提取数据类

最先拆的是数据类定义,因为数据没有依赖,拆起来最安全:

// model/Types.tsexportclassPos2D{x:number=0;y:number=0;constructor(x:number,y:number){this.x=x;this.y=y;}}exportclassStarPt{x:number=0;y:number=0;sz:number=0;constructor(x:number,y:number,sz:number){this.x=x;this.y=y;this.sz=sz;}}exportclassSignData{name:string='';emoji:string='';date:string='';elem:string='';trait:string='';stars:StarPt[]=[];// 新增字段strengths:string='';weaknesses:string='';matches:string[]=[];constructor(n:string,e:string,d:string,el:string,t:string,s:StarPt[],str:string='',wk:string='',mt:string[]=[]){this.name=n;this.emoji=e;this.date=d;this.elem=el;this.trait=t;this.stars=s;this.strengths=str;this.weaknesses=wk;this.matches=mt;}}exportclassTarotInfo{name:string='';emoji:string='';key:string='';text:string='';constructor(n:string,e:string,k:string,t:string){this.name=n;this.emoji=e;this.key=k;this.text=t;}}

为什么先拆数据?

  1. 数据类没有 UI 依赖,拆出去不会影响任何渲染逻辑
  2. 数据类定义清晰,拆分后每个文件的职责一目了然
  3. 其他文件可以import这些类,为后续拆分打基础

第二步:提取星座数据

数据类拆完后,把 12 星座的完整数据也拆出去。这次重构还顺便给每个星座补充了优势、弱点、最佳配对:

// model/SignsData.tsimport{SignData,StarPt}from'./Types';exportconstSIGNS:SignData[]=[newSignData('白羊座','♈','3.21-4.19','火','热情勇敢·行动力强',[newStarPt(100,30,3),newStarPt(60,70,2),newStarPt(140,70,2),newStarPt(100,110,4),newStarPt(100,170,3),newStarPt(40,140,2),newStarPt(160,140,2)],'充满活力,勇于开拓,天生的领导者','冲动急躁,缺乏耐心,容易半途而废',['狮子座','射手座']),// ... 其他 11 个星座];

新增的三个字段

  • strengths:优势描述(用于星图详情的"优势"区域)
  • weaknesses:弱点描述(用于星图详情的"弱点"区域)
  • matches:最佳配对星座数组(用于星图详情的"最佳配对"区域)

第三步:提取塔罗数据

// model/TarotData.tsimport{TarotInfo}from'./Types';exportconstTAROT:TarotInfo[]=[newTarotInfo('愚者','🃏','新开始·冒险','新的旅程即将开始...'),newTarotInfo('魔术师','🪄','创造力·技巧','你拥有实现目标的所有资源...'),// ... 其他 19 张];

第四步:提取常量配置

// model/Constants.tsexportconstWHEEL_COLORS:string[]=['#7B4FBF','#3B6FC2','#20A39E','#E89240','#C44569','#5D5FEF','#2E9C7A','#D4594C','#6B5CE7','#3D8FD1','#18A085','#E07030'];exportconstFORTUNE_LABELS:string[]=['大吉','中吉','小吉','末吉','大吉','中吉','小吉','大吉','中吉','末吉','小吉','中吉'];

颜色和运势标签从 Index.ets 顶部移到了独立文件。以后要改配色,只需要改这一个文件。

第五步:提取页面组件

数据拆完后,开始拆页面组件。这是最关键的一步——把@Builder升级为@Component

转盘组件

// view/WheelView.etsimport{SignData,Pos2D}from'../model/Types';import{SIGNS}from'../model/SignsData';import{WHEEL_COLORS,FORTUNE_LABELS}from'../model/Constants';@Componentstruct WheelView{@StatewheelAngle:number=0;@Statespinning:boolean=false;@StateresultIdx:number=-1;dateSeed(max:number):number{constd:Date=newDate();return(d.getFullYear()*397+d.getMonth()*31+d.getDate())%max;}doSpin():void{if(this.spinning){return;}this.spinning=true;this.resultIdx=-1;this.wheelAngle+=1800+this.dateSeed(360);}doReveal():void{if(!this.spinning){return;}constn:number=((360-(this.wheelAngle%360))%360+360)%360;this.resultIdx=Math.floor(n/30)%12;this.spinning=false;}wheelPos(idx:number):Pos2D{consta:number=-Math.PI/2+idx*(2*Math.PI/12);returnnewPos2D(145-28+105*Math.cos(a),145-28+105*Math.sin(a));}build(){Column(){// 转盘的完整 UI}}}export{WheelView};

关键变化

  1. @Builder变成@Component struct WheelView
  2. 转盘相关的状态(wheelAnglespinningresultIdx)从 Index 移到了 WheelView 里
  3. 转盘相关的方法(doSpindoRevealwheelPos)也跟着移过来了
  4. 通过import引用数据和常量

星图组件

// view/StarView.etsimport{SignData,StarPt,Pos2D}from'../model/Types';import{SIGNS}from'../model/SignsData';@Componentstruct StarView{@StateselectedSign:number=-1;@StateshowDetail:boolean=false;build(){Column(){// 星图页面的完整 UI// 包括:标题、星图详情(优势/弱点/配对)、星座网格}}}export{StarView};

星图组件的新增功能

重构后的星图页面比第一版丰富了很多:

星座星图 点击星座·探索你的星空 ┌─────────────────────────┐ │ 星点可视化 │ │ · · · · · · · │ │ · ♊ · │ │ · · · · · · · │ └─────────────────────────┘ 风象星座 5.21-6.21 聪明灵活·好奇心强 ✨ 优势 思维敏捷,沟通能力出众,能快速适应各种环境 ⚠️ 弱点 注意力分散,容易半途而废,有时显得表里不一 ❤️ 最佳配对 天秤座 水瓶座 [收起详情] [关闭星图] ┌──────┐ ┌──────┐ ┌──────┐ │ 白羊 │ │ 金牛 │ │ 双子 │ ← 双子座选中(金色边框) └──────┘ └──────┘ └──────┘ ...

塔罗组件

// view/TarotView.etsimport{TarotInfo}from'../model/Types';import{TAROT}from'../model/TarotData';@Componentstruct TarotView{@Statedealt:boolean=false;@StatetarotIdx:number[]=[0,1,2];@StatetarotOpen:boolean[]=[false,false,false];shuffle():void{// 洗牌逻辑}doFlip(i:number):void{// 翻牌逻辑}build(){Column(){// 塔罗页面的完整 UI}}}export{TarotView};

第六步:精简 Index.ets

最后一步是精简主入口。Index.ets 现在只负责导航和路由:

// pages/Index.etsimport{WheelView}from'../view/WheelView';import{StarView}from'../view/StarView';import{TarotView}from'../view/TarotView';@Entry@Componentstruct Index{@Statepage:number=0;navTitle():string{if(this.page===0){return'命运转盘';}if(this.page===1){return'星座星图';}return'塔罗牌阵';}navSub():string{if(this.page===0){return'转动转盘·揭晓今日运势';}if(this.page===1){return'点击星座·探索你的星空';}return'默念问题·翻开命运之牌';}navIcon(i:number):string{if(i===0){return'🎡';}if(i===1){return'✨';}return'🃏';}navLabel(i:number):string{if(i===0){return'运势';}if(i===1){return'星图';}return'塔罗';}build(){Column(){// 导航栏Column(){Text(this.navTitle()).fontSize(16).fontWeight(FontWeight.Bold).fontColor('#E8DFF8')Text(this.navSub()).fontSize(10).fontColor('#6B5B9A').margin({top:4})}Row(){ForEach([0,1,2],(i:number)=>{Column(){Text(this.navIcon(i)).fontSize(22).opacity(i===this.page?1:0.4).scale({x:i===this.page?1.15:1,y:i===this.page?1.15:1})Text(this.navLabel(i)).fontSize(11).fontColor(i===this.page?'#FFD700':'#5B4FAF')Divider().width(i===this.page?24:0).height(2).color('#FFD700')}.width(90).onClick(()=>{this.page=i;})})}// Swiper 页面切换Swiper(){WheelView()StarView()TarotView()}.index(this.page).indicator(false).loop(false).duration(300).onChange((idx:number)=>{this.page=idx;}).layoutWeight(1)}.width('100%').height('100%').backgroundColor('#06060F')}}

Index.ets 的变化

项目重构前重构后
代码行数500+~100
@State 数量7 个1 个(page)
@Builder 数量8 个0 个
数据定义内联import
页面逻辑全在 Index各自独立

重构带来的好处

1. 代码可读性提升

每个文件只做一件事:

  • Index.ets:导航和路由
  • WheelView.ets:转盘逻辑
  • StarView.ets:星图逻辑
  • TarotView.ets:塔罗逻辑
  • Types.ts:数据结构
  • SignsData.ts:星座数据
  • TarotData.ts:塔罗数据

找代码不再需要翻来翻去,打开对应的文件就行。

2. 状态隔离

转盘的旋转状态、星图的选择状态、塔罗的翻牌状态各自独立。修改转盘不会触发星图的重新渲染,修改塔罗不会影响导航栏。

3. 复用性提升

如果要做一个"每日塔罗"应用,可以直接复用TarotView.etsTarotData.ts,不需要复制粘贴代码。

4. 维护成本降低

改星座数据只需要改SignsData.ts,改塔罗数据只需要改TarotData.ts,不会影响其他文件。

重构中的踩坑记录

坑 1:@Builder 升级为 @Component 的参数传递

@Builder不需要参数传递(直接访问父组件的this),但@Component需要通过@Prop@Link传递参数。

比如转盘组件需要从外部控制旋转状态,就需要:

@Componentstruct WheelView{@Propspinning:boolean=false;@PropwheelAngle:number=0;// ...}

但这次重构选择了让每个组件管理自己的状态,所以没有用@Prop

坑 2:import 路径的配置

ArkTS 的 import 路径需要正确配置。'../view/WheelView'这种相对路径在大多数情况下可以工作,但如果项目结构复杂,可能需要配置tsconfig.jsonpaths字段。

坑 3:export 的写法

ArkTS 的 export 语法和 TypeScript 略有不同。需要用export { WheelView }或者在struct前加export

坑 4:共享数据的导入

多个组件都需要导入SIGNS数据。如果每个组件都写一遍 import,修改数据文件路径时需要改多个地方。更好的做法是在model/文件夹的index.ts中统一导出:

// model/index.tsexport{Pos2D,StarPt,SignData,TarotInfo}from'./Types';export{SIGNS}from'./SignsData';export{TAROT}from'./TarotData';export{WHEEL_COLORS,FORTUNE_LABELS}from'./Constants';

然后各组件统一从model/导入:

import{SIGNS,SignData,StarPt}from'../model';

重构前后的对比

维度重构前重构后
文件数1 个7 个
最大文件行数500+~150
状态数量7 个 @State1 + 2 + 2 + 2
数据定义内联在组件顶部独立 model/ 文件夹
修改成本改一个可能影响全部改一个只影响一个
复用性无法复用可独立复用

总结

这次重构把一个 500+ 行的单文件拆成了 7 个文件,每个文件职责单一。重构过程中还顺便给星图页面补充了优劣势和最佳配对数据,让应用内容更丰富。

适用边界:这个重构过程适合用作 ArkTS 模块化拆分的实战案例,涵盖了数据提取、组件拆分、状态隔离、import 配置等核心知识点。但如果要上架应用商店,还需要补充状态管理库、路由管理、错误处理、日志系统等内容。建议在此基础上逐步扩展,而不是一次性做完所有功能。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/19 15:28:26

RT-DETR终极指南:如何构建实时端到端目标检测系统

RT-DETR终极指南:如何构建实时端到端目标检测系统 【免费下载链接】RT-DETR [CVPR 2024] Official RT-DETR (RTDETR paddle pytorch), Real-Time DEtection TRansformer, DETRs Beat YOLOs on Real-time Object Detection. 🔥 🔥 &#x1f52…

作者头像 李华
网站建设 2026/7/19 15:27:48

docker安装portainer详细步骤-元一软件

docker安装portainer详细步骤 portainer是一款容器管理可视化界面,不想在虚拟中使用命令管理容器的小伙伴,可以选择安装portainer对容器进行管理,查看日志、启动、停止容器等非常方便。 1. 搜索portainer镜像file 2. 拉取portainer镜像 docke…

作者头像 李华
网站建设 2026/7/19 15:27:40

linux网络管理-iptables基础-元一软件

1.1 四表应用顺序:由上至下raw:对连接做追踪mangle:对数据包做修改,如给数据包打标记nat :修改数据包地址filter:对数据包过滤 1.2 五链 INPUT:针对目标地址OUTPUT:针对源地址FORWAR…

作者头像 李华
网站建设 2026/7/19 15:27:13

python数据可视化技巧的100个练习 -- 26. 平行坐标图用于客户数据分析

重要性★★★★☆ 难度★★★☆☆ 你是一家零售公司的数据分析师。你的经理要求你分析客户数据以识别模式和趋势。你需要创建一个平行坐标图来可视化不同客户属性(如年龄、年收入和消费得分)之间的关系。 生成一个包含以下列的样本数据集:‘Age’(年龄)、‘Annual Inco…

作者头像 李华
网站建设 2026/7/19 15:26:58

【华为OD机试真题 新系统】1043、魔法咒语组合 | 机试真题+思路参考+代码解析(C++、Java、Py、C语言、JS)

文章目录 一、题目 🎃题目描述 🎃输入输出 🎃样例1 🎃样例2 二、代码与思路参考 🎈C++语言思路 🎉C++代码 🎈Java语言思路 🎉Java代码 🎈Python语言思路 🎉Python代码 🎈C语言思路 🎉 C语言代码 🎈JS语言思路 🎉JS代码 作者:KJ.JK 订阅本专栏后即…

作者头像 李华
网站建设 2026/7/19 15:24:12

如何评估humanizer-1B-OptiQ-4bit的输出质量:7个关键指标

如何评估humanizer-1B-OptiQ-4bit的输出质量:7个关键指标 【免费下载链接】humanizer-1B-OptiQ-4bit 项目地址: https://ai.gitcode.com/hf_mirrors/mlx-community/humanizer-1B-OptiQ-4bit humanizer-1B-OptiQ-4bit是一款基于MLX框架的1B参数AI文本人性化模…

作者头像 李华