☰
ArkTS入门:从TypeScript到HarmonyOS应用开发核心技能
2026/9/29 1:12:16 网站建设 项目流程

1. 开篇:为什么我劝你先学ArkTS,而不是直接撸Java或JS

做HarmonyOS开发绕不开一个坎:语言选型。我在社区里见过太多人一上来就问“能不能用Java写”“能不能直接上JS”,结果编译报错、生态不适配、性能调不动,折腾几天又回来啃ArkTS。说实话,与其绕路,不如一开始就把ArkTS当成HarmonyOS应用开发的主语言来学。

ArkTS是HarmonyOS优选的主力应用开发语言。它不是重新造轮子,而是在TypeScript基础上做了一套静态类型增强和运行时约束,再配合ArkUI声明式UI框架使用。你可以把它理解为“TS的超集,但比TS更严格”。

这套语言解决的核心问题有三个:一是抽走了JS动态类型带来的隐性运行时错误,把类型检查提前到编译期;二是通过ArkUI状态管理机制让UI和数据自动同步,省掉了大量手动操作DOM或setState的样板代码;三是为HarmonyOS的方舟编译器做了针对性优化,运行效率明显高于普通JS引擎。

这篇博文面向三类人:完全没接触过HarmonyOS、想从零开始试水的新手;有前端或小程序开发经验、但被ArkTS装饰器和UI语法劝退的开发者;以及已经照着官方文档写过HelloWorld,但对状态管理和工程结构还是一头雾水的朋友。我会从环境搭建、核心语法、UI写法、状态管理到完整小Demo,尽量把这条学习路径讲透。

先给结论:ArkTS入门并不难,难的是把它的“约束思维”和“状态驱动”刻进脑子里。一旦过了这个坎,后面写应用会非常爽。

2. ArkTS到底是个什么“TS”

2.1 为什么华为要做一版“更严”的TS

先聊点背景。HarmonyOS诞生时面临一个现实问题:生态里开发者习惯不一,有的是Java/Android背景,有的是前端JS背景,还有一部分是C/C++底层玩家。这么多语言风格混在一起,生态碎片化会很严重,编译器和运行时也难以做深入优化。

于是华为选择了TypeScript作为基础,因为它本身就有类型系统、有class、有async/await这些现代语言特性,前端开发者容易过渡,同时静态类型又能让方舟编译器做大量编译期优化。但标准TS有一个问题:它太灵活了。比如any满天飞、对象结构随便改、运行时类型完全不可控,这些在大型项目里都是隐患。

ArkTS的思路就是“在TS基础之上做减法”——限制动态能力,强化静态约束,牺牲一部分自由度换取更高性能和更强稳定性。官方文档管这个叫“ArkTS是TS的超集,但禁用了TS中不安全的动态特性”。实际体验下来,用一个词形容就是“更稳”。

2.2 强制类型检查,真的比“写着爽”更重要

如果你写惯了JS,第一次碰ArkTS会有点憋屈。比如在TS里你可以这么写:

let user: any = { name: "张三" }; let age = user.age;

在ArkTS里尽量别这么干。any类型在ArkTS中被限制使用,更推荐显式声明类型。你再看一段:

interface User { name: string; age: number; } const user: User = { name: "张三", age: 20 }; const greeting: string = `你好,${user.name}`;

这样写的好处是:类型错误在编译阶段就被拦住了,而不是运行时才“啪”一下崩溃。对个人开发者来说,减少线上崩溃比写代码时省那几秒重要得多。

2.3 ArkTS和标准TS的关键差异速览

很多从TS转过来的朋友其实不需要重新学一遍语法,只要知道ArkTS“禁止了什么”就行。我列个表,你对照看:

特性TypeScriptArkTS影响
any类型允许受限,推荐unknown或显式类型编码规范更强
对象字面量动态增删属性支持不允许,需先定义interface或class代码更可预期
联合类型支持支持,但需所有分支类型一致逻辑更清晰
函数重载支持支持,但实现需兼容所有签名与TS基本一致
装饰器有,但非标准有,且作为核心特性(ArkUI依赖)学习重点
结构化类型(鸭子类型)支持受限,推荐通过interface显式声明提高类型安全
泛型支持支持,但约束更严格几乎无感

看到这里,你可能会觉得“ArkTS就是个被绑住手脚的TS”。对,它的确没有TS那么奔放,但换来的是更高的运行效率、更低的隐性Bug率,以及和ArkUI状态管理机制的无缝配合。后面写复杂页面时你就会体会到,很多JS项目里常见的“数据改了但界面没变”“对象莫名其妙多了一个属性”这类问题,在ArkTS里几乎绝迹。

3. 环境搭建:把DevEco Studio跑起来

3.1 安装DevEco Studio的完整流程

学ArkTS绕不开官方IDE:DevEco Studio。它是基于IntelliJ IDEA定制的,用过Android Studio或WebStorm的朋友上手零成本。

安装其实没啥玄学,但有几个细节值得注意。第一步去HarmonyOS开发者官网下载DevEco Studio,选对应你操作系统的版本,Windows就下载Windows版,macOS就下载macOS版。下载时注意区分x86_64和Apple Silicon版本,这里不对版本型号做推荐,只提醒你买新不买旧。

安装完成后,第一次启动会引导你下载HarmonyOS SDK和工具链。这个过程根据网络情况可能比较久,建议在网速好的时间段操作。如果你之前装过Node.js或Android SDK,别担心,DevEco Studio会自己管理HarmonyOS那套SDK,彼此的Path互不干扰。

3.2 创建一个ArkTS工程前,先搞懂这4个文件

新建工程时,模板选择“Empty Ability”,语言选“ArkTS”,工程名字别带中文和空格。等初始化完成,你第一眼看到的是项目目录,官方模板已经把该建的东西都建好了。我建议你花10分钟把这些文件翻一遍,尤其是这几个:

entry/src/main/ets/pages/Index.ets:首页UI和逻辑代码,你大部分时间都在写这个文件。

entry/src/main/ets/entryability/EntryAbility.ets:应用入口,类似Android的Application + MainActivity合体,里面管理Ability生命周期。

entry/src/main/resources/base/profile/main_pages.json:页面路由配置,页面要在这里注册才能跳转。

entry/build-profile.json5:模块构建配置,里面可以配签名、混淆、编译选项,新手暂时不用动。

很多人一进来就急着写代码,结果想加个新页面时发现Router跳不过去,回头一查才发现忘了在main_pages.json里注册页面。这个坑我踩过,记清楚了。

3.3 打开模拟器,或者直接用真机

写UI代码最痛苦的事情是“不知道长啥样”。DevEco Studio自带模拟器,但HarmonyOS模拟器对电脑性能要求不低,配置一般的笔记本建议直接用真机。真机调试步骤也不复杂:手机打开“开发者模式”,USB连上电脑后,在DevEco Studio里选择你的设备,直接点Run就能装上App。

如果开了模拟器,第一次启动时会一直转圈,这大概率是在加载系统镜像,给点耐心。模拟器启动后,你在端侧的视觉是和真机一致的,字体渲染、屏幕适配、折叠屏切换这些都能模拟到。

4. ArkTS基础语法:手把手过一遍

4.1 变量声明和类型推断,为什么推荐const配let

变量声明这块和TS几乎一样,但有一个我特别想强调的习惯:能用const就不要用let。原因不是“正统”,而是ArkTS配合方舟编译器做优化时,对不可变数据的处理效率明显更高,代码也更好推理。

const appName: string = "我的应用"; let count: number = 0; count = count + 1; // OK,count用let声明,值可变 appName = "新名字"; // 编译报错,const声明的引用不可再赋值

另外,ArkTS支持类型自动推断,很多时候你不需要手写类型注解。但建议在对外接口、公共方法、组件属性这些“边界位置”显式声明类型,方便别人(以及未来的你)快速读懂数据流向。

4.2 interface、class与数据建模

ArkTS里操作数据,最常用的方式就是定义interface来约束数据形状。比如一个待办事项的样子:

export interface TodoItem { id: number; title: string; completed: boolean; createdAt: number; }

定义好接口之后,创建、修改数据的时候就不会出现“对象字段名拼错”这种低级问题。此外,当数据结构和业务逻辑更复杂时,推荐用class来做数据模型,可以把操作数据的方法也装进去:

export class TodoModel { items: TodoItem[] = []; addItem(title: string): void { const newItem: TodoItem = { id: Date.now(), title: title, completed: false, createdAt: Date.now(), }; this.items.push(newItem); } toggleItem(id: number): void { const target = this.items.find(item => item.id === id); if (target) { target.completed = !target.completed; } } }

这种“数据模型自带方法”的写法,比在页面里堆一堆工具函数要干净得多,也好测试。

4.3 装饰器:ArkTS的灵魂,也是ArkUI的基石

学ArkTS,你一定会撞上@Entry、@Component、@State这些装饰器。第一次见会觉得“这是什么鬼”,但理解了它的设计意图之后就简单了:装饰器就是在编译阶段对类、属性、方法做标记和增强,让框架知道“这个类是个页面”“这个属性是个响应式状态”。

举个最常见的例子:

@Entry @Component struct IndexPage { @State message: string = "Hello HarmonyOS"; build() { Column() { Text(this.message) .fontSize(30) .fontWeight(FontWeight.Bold) Button("修改文案") .onClick(() => { this.message = "文案已被修改"; }) } .width('100%') .height('100%') .justifyContent(FlexAlign.Center) } }

@Entry标记这个组件是一个页面入口;@Component声明它是一个UI组件;@State把message变成响应式状态,当它改变时,依赖它的Text组件会自动刷新。这里没有手动监听数据、没有setState、没有重新渲染调用,数据变了UI就跟着变,这就是ArkUI的“状态驱动UI”。

4.4 生命周期:页面从生到死要经过哪些阶段

ArkTS页面组件的生命周期主要在aboutToAppear、aboutToDisappear和onPageShow、onPageHide这几个函数里。前者是组件从创建到销毁的完整过程,后者是页面在前台后台切换时触发。

@Entry @Component struct LifecycleDemo { aboutToAppear() { console.info("组件即将显示,适合初始化数据"); } aboutToDisappear() { console.info("组件即将销毁,适合释放资源"); } onPageShow() { console.info("页面显示,适合做数据刷新"); } onPageHide() { console.info("页面隐藏,适合暂停播放或停止轮询"); } build() { Text("生命周期示例") } }

新手容易混淆的是aboutToAppear和onPageShow的触发时机。简单记:组件挂载时先走aboutToAppear,页面完整可见后再走onPageShow。所以拿数据接口的请求一般放在aboutToAppear,而从别的页面返回时的刷新逻辑放在onPageShow更合适。

5. UI怎么搭:认识ArkUI的声明式组件

5.1 布局容器的关键属性:Column、Row、Stack

ArkUI的UI写法从代码结构上看,很像Flutter的Widget树,又有点像小程序模板和CSS的组合体。最常用的三个布局容器是:

  • Column:子组件垂直排列
  • Row:子组件水平排列
  • Stack:子组件层叠排列(适合做悬浮按钮、遮罩层)

每个容器都有width、height、padding、margin、justifyContent和alignItems这些布局属性,玩过Flexbox的人几乎不用学就会。唯一的门槛是属性写法和CSS有些不同,比如CSS的justify-content: center在ArkUI是.justifyContent(FlexAlign.Center)。

例子:

Column() { Text("标题") .fontSize(24) .margin({ bottom: 12 }) Row() { Text("左") Text("右") } .width('100%') .justifyContent(FlexAlign.SpaceBetween) } .width('100%') .padding(16)

5.2 常用组件清单:Text、Button、Image、List、ForEach

组件这块不用记太多,用到什么查什么就行。但有几个高频组件建议重点掌握:

Text:文本展示,支持fontSize、fontColor、fontWeight、textAlign等属性,还可以通过$r('app.string.xxx')引用资源文件里的文案,做多语言时很好用。

Button:按钮组件,除了onClick回调,还有type(胶囊、普通)、backgroundColor等UI属性。按钮里放文字直接字符串即可,也可以套其他组件。

Image:图片组件,加载网络图时直接传URL,加载本地资源用$r('app.media.icon')。注意网络图片需要申请ohos.permission.INTERNET权限,否则请求会直接失败。

List+ForEach:列表类页面的核心方案。ForEach是循环渲染的语法,类似JS的map,接收一个数组和一个item渲染函数:

List() { ForEach(this.todoItems, (item: TodoItem) => { ListItem() { Row() { Text(item.title) Text(item.completed ? "已完成" : "未完成") } } }, (item: TodoItem) => item.id.toString()) }

ForEach的第三个参数是唯一键生成函数,强烈建议传一个能唯一区分的字段(比如id),这样列表增删改时ArkUI能精确对应到每一条数据,避免渲染错乱和性能浪费。

5.3 别忘了,样式是写链式调用,不是className

ArkUI组件样式最独特的一点是:每个组件可以通过链式调用任意数量的属性方法。你要一个文字变蓝、变大、加粗,就一路点下去:

Text("ArkTS入门") .fontSize(28) .fontColor(Color.Blue) .fontWeight(FontWeight.Bold) .textAlign(TextAlign.Center) .padding({ top: 10, bottom: 10 })

这和Web里的class="title"加CSS文件的套路完全不同,好处是样式离组件很近,改起来不跳文件,坏处是一旦组件复杂了,链式调用会特别长。解决办法是抽取公共样式组件,或者把重复的样式封装成@Styles和@Extend,后面在进阶内容再展开。

6. 状态管理:ArkTS里最值钱的部分

6.1 单向数据流:从@State到@Prop,再到@Link

状态管理是ArkTS相对其他前端框架最显著的分水岭。核心思想是“状态驱动视图”,状态变了,视图自动更新。但状态绝对不能在组件之间随意乱传,必须遵循一套规则,不然页面一复杂就乱套。

先说单组件内的状态:用@State。它只能被当前组件“拥有”,子组件不能直接改父组件的@State。要让子组件读到父组件的状态,可以用@Prop。@Prop是单向的:父组件把值传进来,子组件可以读、可以改副本,但改的是自己的本地副本,不会反向影响父组件。

那子组件想直接改父组件的状态怎么办?用@Link。它建立的是双向绑定,父组件把状态传给子组件后,子组件修改一个@Link变量,父组件里对应的值也会同步变化。

@Entry @Component struct ParentComponent { @State count: number = 0; build() { Column() { ChildComponent count={this.count} Button("父组件自增") .onClick(() => { this.count++; }) } } } @Component struct ChildComponent { @Link count: number; build() { Button("子组件自增") .onClick(() => { this.count++; }) } }

上面这段代码里,ChildComponent通过@Link接收父组件的count,在子组件里点击按钮,父组件的count也跟着变。注意@Link变量初始化时不能给默认值,它是靠父组件传进来的。

6.2 对象/数组里的字段变了,为什么视图不刷新

用@State装饰的对象,如果直接修改对象里某个字段,ArkUI有时不会触发刷新。为什么?因为@State是浅观察,只监听属性本身的重赋值,不递归监听对象内部字段的每个变化。这种时候需要用@Observed和@ObjectLink。

@Observed装饰类,让类实例变成可观察的;@ObjectLink装饰该类的属性,在子组件里精确观察这个对象的变化。看代码片段理解更快:

@Observed export class UserInfo { name: string = ""; age: number = 0; constructor(name: string, age: number) { this.name = name; this.age = age; } } @Component struct UserCard { @ObjectLink user: UserInfo; build() { Column() { Text(`姓名:${this.user.name}`) Text(`年龄:${this.user.age}`) Button("年龄+1") .onClick(() => { this.user.age++; }) } } }

在这个例子里,UserInfo类被@Observed标记后,UserCard用@ObjectLink接收对象,修改user.age时视图会可靠刷新。这是处理复杂数据模型状态更新的正解。记住一句话:值类型和数组重赋值用@State,对象内部字段联动刷新用@Observed配@ObjectLink。

6.3 简单状态还是用@State,重状态管理再上@StorageLink/@Provide

有些变量需要跨页面共享,比如用户的登录状态、App主题颜色。这时用@State加父子传递就太累了,跨页面还会遇到参数传递的地狱。

ArkTS提供两种方案。一种是“本地存储会话级状态”,用@StorageLink绑定到AppStorage,它是个全局键值存储,页面销毁后值还在,App重启后可以保留一部分持久化数据。另一种是做依赖注入,用@Provide在父组件提供状态,用@Consume在后代组件里注入使用,类似React的Context。

我个人的项目习惯是:三四个组件以内的共享状态用@Link,超过这个层级或跨页面共享才考虑@StorageLink或@Provide/@Consume。状态管理方案不是越强越好,而是越简单越好,新手尤其别一上来就整花活。

7. 实战:做一个带增删改查的待办清单

7.1 数据模型和组件拆分

趁热打铁,写一个完整的小Demo:待办清单。功能包括添加任务、标记完成、删除任务。先定义数据模型:

export class TodoItemModel { id: number; title: string; completed: boolean; constructor(title: string) { this.id = Date.now(); this.title = title; this.completed = false; } }

页面结构拆成三块:顶部输入区、统计栏、列表区。为了不把页面写成一坨,建议把“单条待办”抽成一个子组件。

7.2 核心页面代码:专注看状态如何流转

完整代码我整理了一个最小可运行版本,你复制到新建的Index.ets里就能跑:

@Entry @Component struct TodoListPage { @State todos: TodoItemModel[] = []; @State inputValue: string = ""; private inputController: TextInputController = new TextInputController(); build() { Column({ space: 16 }) { Row({ space: 8 }) { TextInput({ placeholder: "请输入待办内容", text: this.inputValue }) .layoutWeight(1) .onChange((value: string) => { this.inputValue = value; }) Button("添加") .onClick(() => { this.addTodo(); }) } .width('100%') Text(`共 ${this.todos.length} 条待办`) List() { ForEach(this.todos, (item: TodoItemModel) => { ListItem() { Row() { Checkbox() .select(item.completed) .onChange((checked: boolean) => { this.toggleTodo(item.id, checked); }) Text(item.title) .decoration({ type: item.completed ? TextDecorationType.LineThrough : TextDecorationType.None }) .layoutWeight(1) Button("删除") .onClick(() => { this.removeTodo(item.id); }) } } }, (item: TodoItemModel) => item.id.toString()) } .layoutWeight(1) .width('100%') } .width('100%') .height('100%') .padding(16) } addTodo(): void { const title = this.inputValue.trim(); if (title.length === 0) { return; } this.todos.push(new TodoItemModel(title)); this.inputValue = ""; this.inputController.caretPosition(0); } toggleTodo(id: number, checked: boolean): void { const target = this.todos.find(item => item.id === id); if (target) { target.completed = checked; } } removeTodo(id: number): void { const index = this.todos.findIndex(item => item.id === id); if (index !== -1) { this.todos.splice(index, 1); } } }

这个Demo麻雀虽小,但五脏俱全。你注意看:todos数组用@State修饰,任何push、splice、修改字段的操作,都会自动触发列表刷新。你不需要手动去操作DOM节点,也不需要调用任何刷新方法,要做的只是修改数据,剩下的交给框架。

7.3 一个容易被忽略的坑:数组的方法要用能触发更新的那些

新手写removeTodo时很容易踩一个坑:用this.todos.filter()生成新数组,然后直接this.todos = filteredArray。这种做法在ArkTS里其实也能触发更新,因为数组整体被重新赋值了,但我更推荐用splice这类原地修改方法。

为什么?一是性能,splice不会产生新的大数组;二是按官方推荐,@State修饰的数组要对该数组的方法保持敏感,原地修改的兼容性更好。实际开发中,push、splice、pop、shift、unshift这些原地修改方法都可以放心用。

8. 编译报错和运行异常排查一览

8.1 新手高频报错Top 5,遇到别再慌

新手期编译报错是常态,我整理了碰到的频率最高的几个:

报错信息常见原因解决办法
Property 'xxx' does not exist on type类型声明里没有这个属性去interface或class里补全字段
Argument of type 'string' is not assignable to parameter of type 'number'类型不匹配检查赋值语句的类型,不要硬传
Cannot find name 'xxx'忘记import,或拼写错误检查文件头部import语句
@State decorated property is not initialized@State变量声明时没有赋初值给@State变量一个默认值
Object literal must correspond to some explicitly declared class or interfaceArkTS不允许裸对象字面量先定义class或interface,再用它来约束

我特别想提醒的是报错信息里带Object literal must correspond to...这条。很多JS/TS老手一看就懵:对象字面量怎么就不行了?其实ArkTS要求对象必须要有明确的类型形状,所以你直接const obj = { name: "张三" }没问题,但如果往里面动态塞属性就会报错。正确做法是先定义接口:

interface Person { name: string; age?: number; } const p: Person = { name: "张三" };

8.2 页面空白不报错,多半是布局或状态问题

有时候编译通过了,但页面上啥也没有。这种“静默失败”最烦人。根据我的经验排查顺序是:

第一步看日志。DevEco Studio的Log窗口会打印应用运行日志,报错信息大概率藏在里面。

第二步看布局。有没有可能是容器高度塌陷了?子组件没占满空间?很多空白页是因为子组件往Column或Row塞,但外层容器没有layoutWeight或自适应高度。

第三步看数据源。@State数组是不是空?接口没返回数据?可以临时在页面加一行Text(JSON.stringify(this.todos))打出来看看。

第四步看生命周期。如果数据在异步回调里才拿到,组件销毁后才赋值,也会出现“数据没刷新到UI”的错觉。检查是不是忘了解除订阅,或者回调时机不对。

8.3 DevEco Studio相关:模拟器卡顿、预览器不刷新

模拟器卡顿是常见问题,尤其在你电脑内存只有8GB时。我建议把模拟器分辨率调低、关闭动画缩放,或者直接换真机。真机调试的实时性、性能表现完胜模拟器。

预览器不刷新又是另一个经典场景:你改了代码但右侧Preview没反应。先把工程关掉重新打开一次,还不行就清一下build缓存;如果项目里有编译错误,预览器也会罢工,先把报错解决。总之,遇到这类问题别急着重装IDE,重启、清缓存、看日志三步走。

9. 从入门到能干活,你还需要再补这些课

到这儿,你已经能写一个基本的ArkTS页面了。但从“会写Demo”到“能上架应用”,中间还有几个硬骨头。

首先是模块化和路由。ArkTS应用也建议按功能拆module,公共代码放一个模块,业务代码放另一个模块,跨模块用@ohos.router或Navigation做页面跳转。这块入门时可以先不折腾,但要时刻想着“页面会多起来”。

其次是网络请求和数据持久化。应用不可能全写死在本地。用@ohos.net.http发请求拿到JSON后解析成interface或class,存到首选项或数据库里。网络这块要特别留意异步回调里的错误处理,数据解析失败时要有兜底逻辑,别一崩就闪退。

再然后是性能优化。列表用LazyForEach懒加载替换ForEach;图片统一走缓存和压缩;频繁更新的状态尽量局部化;构造复杂页面时用@Builder抽取子UI段,避免一个build方法写几千行。

最后是工程化。正式项目建议引入ESLint做静态检查、配好单元测试框架、在CI里集成构建和签名。ArkTS的工程体系已经比较成熟,从DevEco Studio的工程设置里就能看到签名证书、混淆、打包这些选项。

我不想在这个入门博文里一次性把所有进阶内容全灌给你,但有一点一定要说:写HarmonyOS应用,语言只是第一步,真正让你产生竞争力的,是把这个系统的能力边界摸透,而且实践中的工程习惯比语法更难改。

10. 最后,说说我踩过的那些坑和个人的心得

文章写到这,主体内容该讲的都讲了,但作为一个过来人,我还是想多啰嗦几句。

第一个心得是:不要拿着Android或Web的思维硬套ArkTS。尤其不要动不动就想着操作DOM、找View、手动刷新。ArkTS的数据驱动模型和这些完全不同,你得学会“只改数据,别管UI”。一开始肯定不习惯,但一旦你试过“一次都没手动操作视图,页面却完美更新”的体验,就再也回不去了。

第二个心得是:报错不要怕,但一定要看全。ArkTS编译器提示其实做得相当友好,一行报错信息能直接定位到文件和列号。很多时候不是语言难,是你没静下心来看那几行报错指向。

第三个心得是:官方文档和示例工程是最好的老师。DevEco Studio新建工程时附带的模板,以及官方提供的各种场景化Demo代码,都值得一行一行读、亲手改。网上很多二手资料不仅过时,甚至还是基于早期API的,看到@State用法对不上号时,先回查官方文档。

最后再分享一个小技巧:写ArkTS界面时,尽量保持一个页面的build方法不要太长。如果某个区块重复出现,就拆成子组件或@Builder函数;如果页面内逻辑逐渐变多,就抽到独立的ViewModel或控制器里,让UI文件尽量只做“视图和状态绑定”这一件事。时间久了你回头看,这个习惯保住了你那些复杂项目的命。

HarmonyOS生态还处在高速成长期,ArkTS作为主力语言,短期不会有太大变动,狠下心来花一两周过一遍,后面写应用会顺手得多。希望这篇入门指南能帮你省掉我当初交的那些学费。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询