前言
学习HarmonyOS也有一段时间了,发现了一些问题,也有一些体会,写在这里, 也是对自己近期的一个学习体会的总结。因为我之前是学Java的,没有安卓的相关开发经验,只有玩过一段时间的微信小程序,所以以下内容,如有错误,敬请指正。
吐槽
-
学习HarmonyOS最大的一个问题就是目前手头上没有合适的华为手机,借了一个Nova5Pro,结果当时不能升级到3.0系统,后面升级到了3.0系统之后,API版本还是6,导致一直都没有真机做测试,使用模拟器和预览器也勉强够用,不过学习过程中也有遇到一些Bug,后面再说
-
第二个问题就是HarmonyOS的版本跟API的版本,个人感觉有点混乱,因为你手上拿着一个设备,你是完全不知道它支持的API版本是多少,此处强烈建议华为的人想办法解决一下这个问题,有一个最简单的方式就是直接把API版本写在手机的开发者模式,这样进去就知道当前手机的API版本,这样不是比较简单吗?
-
感觉官方文档对于初学者来说并不是很友好,学习路线并不线性,官方文档表示,后期鸿蒙开发的主力肯定是ArkaTS+Storg模型,所以我选择直接先从API8入手,学习ArkTS,但是发现,对于ArkTS的学习,我个人之前只学过JS,对于TS的语法很多时候看到的时候一脸懵逼,但是官方文档对于这块内容好像默认你都会似的,也没写,只能自己去查,去摸索,增加了我的学习成本。
-
CodeLabs的形式非常好,有用的内容也不少,但是一开始,我随便打开一个CodeLabs也是一脸懵逼的,为什么这么多文件,为什么这么多引用,对于初学者而言,不是应该简单点好理解一些比较好吗,但是CodeLabs目前的形式,个人感觉有点吓到我,并且对于CodeLabs的引导,给我一种,“我们来学习,这是1,这是2,这是3,1+1=2,1+2=3,你学会了吗?好了现在我们来造航母吧!”我个人感觉非常难受。不知道其他人会不会跟我有同样感觉。
吐槽就先吐槽到这里,接下来对目前学习的一个内容进行一个总结。
知识总结
对于这块内容,我是站在一个新手的角度得出的内容,如有不对的内容,请指正
入门:
基础:
了解:
熟悉:
重要
- 完成快速入门的案例编写,以此体会创建项目、目录结构、配置文件及装饰器、组件等相关概念
- API8页面跳转案例
- API9页面跳转案例
TS通用语法
类型
变量声明
name:type = value
复制
联合类型
- 打个比方说image的src属性,传进去的参数可以是字符串也可以传一个资源路径,为什么可以这样呢?点进去看一下,他是这么写的:
src: string | PixelMap | Resource
复制
- 在类型定义的时候,是用了"|",意思是可以是这个类型,也可以是另一个类型
数组类型
- 最基础的很简单:
let numberArray: number[] = [1, 2, 3] // var numberArray = [1, 2, 3];
let numberArray1: Array<number> = [4, 5, 6] // var numberArray1 = [4, 5, 6];
复制
- 联合类型也可以用数组:
let arr: (string | number)[] = [1, 'a']
复制
- 复杂对象也可以用数组:
let arr: {name: string, age: number}[] = [
{name: 'Jane', age: 17},
{name: 'Jian', age: 20}
]
复制
元组
-
元组算是一个新东西,Python也有用到,本质上就是一个确定数量和类型的数组
-
类型和位置需要一一对应
let data: [number, string, boolean] = [2, '3', true] // var data = [2, '3', true];
复制
枚举
- 这个也是使用enum枚举关键字,可以为枚举项赋值,不赋值默认从0开始,赋值后,这个值就是该枚举中枚举项的下标
enum Direction {
Up = 1, // 不赋值默认从0开始
Down,
Left,
Right
}
console.log(Direction.Down) // 2
console.log(Direction[1]) // Up
复制
类型断言
- 逻辑类似于Java的类型强转,有两种实现方式
str as string
<string>str //这两种方式都是将变量str,强制变成string类型
复制
类型别名
- 使用type关键字可以给某一个Object对象起一个临时性的别名
type Lady = {name: string, age: number} // 对象类型可以使用类型别名
let arr: Lady[] = [
{name: 'Jane', age: 17},
{name: 'Jian', age: 20}
]
复制
接口
接口提供一种数据结构的抽象定义,跟Java的会有所区别,不仅有方法的定义也有属性的定义,定义完了他就是一种数据结构
interface MyInterface {
name: string,
age: number,
say(): string
}
function fn(opts: MyInterface) {
//...
}
fn({
name:"张三",
age:18,
say(){
return "哈哈哈"
}
})
复制
接口的实现与继承
- 跟Java一样,类使用implement关键字实现接口,实现接口后需要实现接口中的方法
- 可以实现多个接口
- 接口之间可以继承,A接口可以继承B接口和C接口,使用Extends关键字
类
属性声明
- 跟Java有所区别,属性默认是Public的
- Public在所有地方都可以访问
- protectd受保护,在类内部和其子类中能够访问,实例中不能访问
- private:私有,只能在内部访问。
- readonly:只读,实例不能对这个属性赋值
构造函数关键字
- 在类中,使用constructor关键字声明构造函数
静态成员
- 跟Java一样,使用Static关键字修饰,修饰后无法使用this访问,必须使用类名调用和修改
存取器
- 使用get\set修饰属性的同名函数,给私有属性一个存取方式,在存的时候可以设定一定的规则,不符合规则的不存
抽象类
- 跟Java一样,抽象类不能用来创建对象,只是专门用来被继承的
- 抽象类中可以有抽象方法和实例方法,抽象方法必须被子类实现,实例方法子类的实例对象可以直接调用
函数
函数类型
-
在TS中,函数有两种类型,一种是函数声明,其实就是先定义函数,然后用到的时候,使用函数名调用
-
另一种类似于Java的匿名函数,用到的时候直接使用function关键字创建一个函数
-
函数的返回值跟属性的返回值一样,需要使用“:返回类型”
//函数声明
function sum(x: number, y: string): string {
return x + y
}
//函数表达式 这种情况,sum的类型是ts自动推断得出的string类型
let sum = function (x: number, y: string): string {
return x + y
}
复制
- 函数也可以成为属性的一种类型
let sum:(x:number,y:string)=>string
//属性sum为一个函数,函数形参为(x:number,y:string),返回值为string
复制
- 函数表达式也可以简化成为箭头函数
(形参)=>{
运算逻辑
}
复制
可选参数
-
当在组件或者函数中,某些参数可传可不传,就可以用到可选参数,使用方式如下,
-
注意,如果一个函数中同时有必选参数和可选参数,可选参数一定要在后面
add(num?:number){
}
复制
默认参数
- 当使用上面的写法的时候,num是没有默认值的,如果我们在函数内有用到该参数,这样就可能会报错,这个时候可以使用默认值的方式,这样参数也是可以省略的,如果省略就会使用默认值
add(num:number=3){
}
复制
剩余参数
- 在Java中,这个叫可变参数,在指定函数的参数个数时,可能无法确定,那么可以将最后一个参数封装成为一个集合,然后在集合中遍历取出参数
function sum(x: number, ...rest: string[]): string[] {
return rest.map(i => i + x)
}
复制
函数重载
- 在Java中也是有方法重载,同名的方法,根据传参的内容不同调用不同的方法,但是在Ts中,函数的每一个形参可以指定不同的参数类型,那么可以在函数内部进行参数判定,但是这种会比较麻烦,所以能用重载也可以用重载
function fn(x: number, y: number): number
function fn(x: string, y: string): string
function fn(x: string|number, y: string|number): string|number{
if(typeof x === 'number' && typeof y === 'number') {
return x + y
} else if(typeof x === 'string' && typeof y === 'string') {
return x + '_' + y
} else {
return '错误'
}
}
// 如果没有前两个重载这里不会报错,输出内容是“错误”
// 如果有了前两个重载这里就会报错,因为此时的参数不符合重载函数的任何一种传参数据类型
fn(10, 'kkk')
复制
泛型
泛型函数
- 泛型函数跟Java一样,可以让函数的参数类型在使用时决定。
泛型接口
- 泛型接口跟泛型函数差不多
泛型类
- 其实跟Java也差不多
TS中的This
- 如果在箭头函数中,this指向的是当前组件,
- 但是在如果使用匿名函数的方式,需要在事件后.bind(this)
ArkTS语法
UI描述
UI组件
-
无参数构造组件
-
有参数构造组件
组件名(参数){
子组件
}
复制
属性方法
- 通过链式调用的方式
- 参数可以传正常的参数外,可以使用枚举类型,具体的可以按进去,看需要传入什么参数
事件方法
- 为当前组件绑定事件,当事件发生时可以进行响应
- 使用方式有三种,一般建议使用前两种
- 直接写箭头函数,
- 把函数定义在外面,然后通过This.functionName调用(不用加括号)
- 使用匿名函数,但是当函数体里面用到this关键字时,需要.bind(this),这个this指向当前组件
子组件配置
- 当一个组件可以包含子组件时,子组件需要写在{ }里面
- 子组件也可以嵌套其他的子组件
装饰器
带@ 就是装饰器,如@State、@Entry
组件定义装饰器
@Component
- 简单理解,被修饰后,这就是一个组件了,可以复用
- 如果想给其他页面复用,需要使用export关键字,然后对应页面使用 import关键字即可导入使用
@Entry
- 如果你想让一个组件单独以一个页面展示,那就需要用到这个修饰器,没什么好说的
- 切记:是一个页面一个,我当时傻傻的以为入口嘛应该就一个,三个页面就index页面加了这个,结果毫无疑问的报错了
@Rreview
- 学会用预览器之后就会发现,使用预览器还是挺爽的,但是预览器只能预览页面,也就是有加@Entry的组件,如果我想预览自定义组件怎么办呢?这就需要用到@Preview了,给自定义组件加上这个,就可以以组件的形式去预览了,所见即所得,还是挺爽的。
动态构建装饰器
@Builder
- 这个装饰器可以修饰一个函数,函数内部定义一个组件,可以达到复用的目的
- 在定义动态组件的时候,可以声明一些参数,然后在组件内部使用它,这样当动态组件被使用的时候,可以传参进去,就达到动态复用的目的
@Styles
- 这个装饰器是用来降低重复的样式代码,不过它里面只能声明一些通用属性,不能声明组件特有的样式。
- 并且两种使用方式:要么就在builder函数之前,要么就在当前组件之外,这样要加一个Function关键字
@Extend
- 如果你需要用到很多个Text组件,然后又不得不为他们定义相同的样式的时候,可以试试@Extend关键字,它可以给基础组件快速扩展同一类样式。
- 这个装饰器只能声明在当前组件的外面,并且也要Function关键字
@CustonDialog
- 使用这个注解可以让你声明一个弹出框,并在想要的时候展示他
- 步骤大概是,
- 创建一个弹窗组件
- 然后在父组件中创建对应的控制器,然后在控制器的构造函数中传入此弹窗组件及其他参数
- 为当前页面的某一个组件绑定事件,当事件触发时,执行弹窗控制器的open方法
- 具体的使用方式参照可以点击这里
状态管理装饰器
@State
- 这个装饰器用来修饰组件内部的变量,当该变量发生变化时,页面上应用该变量的内容会重新刷新
- 这个只是单向绑定,修改值时并不会自动修改变量,所以需要绑定事件,在事件里修改变量,比如当这个值绑定一个输入框的时候,输入框中值的变化不会同步到该变量上,需要手动去修改
- 在声明时要给定初始值
@Prop
- 这个装饰器修饰的变量声明在子组件中,但是初始化由父组件完成,父组件创建该子组件时,需要使用父组件的@State变量去初始化它
- 也可以理解为父组件的@State变量绑定到了子组件的@Prop变量上,当父组件的@State变量发生变化时,使用到该@Prop变量的组件也会发生变化,但是反过来就不会
- 简单来说就是声明一个需要父组件传递过来的变量,谁用到我这个组件,谁就要给我传这个变量
@Link
-
这个装饰器算是增强版的@Prop,因为它可以让父组件中的@State变量跟子组件中@Link修饰的变量联动起来,当任一一方对变量进行修改时,会同步到另一方。
-
值类型必须相同,不能在组件内部初始化,必须接收父组件的@State变量初始化
-
使用时需要使用$关键字,
子组件变量:$父组件变量
如text:$text
复制
@Watch
-
这个装饰器用来监视某一个状态变量的变化,当发生变化时,就会调用该装饰器注册的函数
-
使用方式为:
// @State @Watch("函数名") 变量名:变量类型= 初始值
@State @Watch("add") num:number = 0
//当num每一次发生变化时,就执行一次Add函数
add(){
this.xxx+=1
}
复制
@Consume和@Provide
-
@Provide为数据的提供方,可以讲数据同步给其他被@Consume修饰的变量,但是前提是,变量名需要一样
-
@Consume为数据的接收方,本身不可以自行初始化变量,当感知到@Provide数据的更新时,会触发当前组件的重新渲染。
-
在使用@Provide和@Consume时,要避免循环引用导致死循环。
渲染控制
if/else
- 这是很好理解的吧,为true就渲染该组件,为False就执行Else里面的组件
ForEach
- 这个也很好理解,特别适合于那种样式一样只有内容不一样的组件内容填充
- 使用需要三个东西,遍历的集合,渲染子组件的方法,生成Key值的方法
LazyForEach
- 数据懒加载,使用这个它不会一下子把所有的加载出来,而是按需加载和创建
组件
容器组件
- 弹性盒容器组件,默认是水平的主轴,跟垂直的交叉轴,也就是说是横向排列的
- 可以设定FlexDirection,改变主轴的方向,变为列排列之类的等等
- 也可以设定justifyContent,是主轴的对齐方式方式
- 也可以设定AlignItems,设定交叉轴的对齐方式
- 具体的可以看文档
- 行组件,容器内元素以行排列,也没啥好说的
- 列默认的宽度是100%,横向占满一行
- RowSplit:每个子组件加一道分割线
- 列组件,容器内元素以列排列,更没啥好说的
- 列默认的高度是100%,纵向占满一列
- ColumnSplit :每个子组件之间加一道分割线
- 列表组件,常用于搭配ForEach实现统一类型元素的渲染
- 内含ListItem
- 导航组件,内含TabContent作为页签,每一个页签对应着一个内容视图
- 路由容器组件,常用来套在某一个元素外面,为这个元素添加一个跳转效果,当该元素被点击时就执行跳转
- 堆叠容器,就是栈的排列方式,先放进去的在最底下,然后一层一层铺上去
- 相对布局组件,但是从API9才开始支持,API8不能用
- 可以设置容器内组件相对于其他容器,或者相对于父容器的位置,相对来说比较自由,但是也比较繁琐的一个布局
- 滑动轮播组件,可以滑动,也可以轮播
- 栅格容器组件,搭配GridCol使用,
- 可以理解为把屏幕按比例分成若干个栅格,布局按栅格划分,在不同的屏幕尺寸上,栅格大小是不同的,所以占用屏幕尺寸也是会等比例缩小或者放大,从而实现屏幕大小的自适应
- 网格容器,由行和列分割的单元格组成,由GridItem占用格的大小,来展现布局
- 可以理解为一种可滑动的弹出框,可以用作应用内通知栏
- 可以滚动的容器组件,当子组件布局尺寸超过父组件时,可以滚动
- 侧边栏组件,传入的第一个组件为侧边栏,就是展开的时候会显示出来的东西,第二个组件是主页面组件
- 下拉刷新动效,具体可以看文档
- 标识组件,就是类似于微信的未读提醒
- 计数器组件,在值的两侧有一个加减符号,可以分别设定回调函数,以实现加减功能
- 可以加一个input组件,实现可以加减,也可以输入的功能
- 目前有个Bug,当含有子组件时,设置子组件的margin属性,此时子组件就消失了
- 字母索引条,一般用在通讯录应用啥的,
基础组件
- 按钮组件,具体看文档
- 图片组件,具体看文档
- 文本组件,具体看文档
- 可以添加Span组件,用于给文本做一定装饰
- 文本输入框组件,具体看文档
- 这个组件一定程度可以让我们的布局设计更简单,因为它是默认占据剩余的空白空间
- 仅当父组件为Row/Column时生效,当父组件不设置宽度时,Blank失效,所以可以设置个最小宽度
- 分割器组件
- 加载动效的组件,具体看文档
- 单选框组件,没啥说的,看文档
- 多选框组件,与全选,看文档
- 有一点需要特别注意,当有用到CheckboxGroup时,该组件必须在最上面
- 勾选框样式、状态按钮样式、开关样式
- 在使用时需要指定样式与默认状态
- 数据面板组件,有线形跟环形两种样式,用于体现数据占比
滑动选择器
- DatePicker:日期选择器
- TextPicker:文本选择器
- TimePicker:时间选择器
- 这些选择器可以搭配Panel组件或者弹窗组件来进行弹出选择
- 跑马灯组件,当文本内容宽度超出组件宽度时,实现跑马灯样式的滚动效果
- 页面的根组件,可以用来设置页面的标题、工具栏、菜单等内容
TextClock
- 将当前时间以文本形式展示
- 文本计时器组件,可以正计时,倒计时、
- 控制器可以开始、暂停、重置
下拉选择菜单
- Select:给定一个数组,返回选择的数组下标
评分条
- Rating:可用于电影评分之类的场景
滑动条
- Slider:可用于滑动调节值
进度条
- Progress:各种样式的进度条组件
二维码
- QRCode:可以将Value转化成二维码图片进行展示
搜索框
- Search:没啥说的
分割器
- Divider:提供一个分割条的样式,仅此而已
图表
- Gauge:构建 一个环形图表,然后有一个指针指向当前数据所在的位置,可以理解为汽车上的速度指示器那种样式
媒体组件
Video
- 视频播放组件,这个需要用到的时候,再去看吧
弹窗
- 其他的弹窗也都差不多,可以看文档
资源的使用
- 在开发过程中,肯定需要用到很多的资源,比如定义好的字符串、图片音频等,这些资源文件统一放在Resources目录下
- 使用方式1:$r("app.type.name") 此方式可以调用base目录下的资源,切记,此方式下,不支持字符串拼接
- 使用方式2:$rawfile("filename") 此方式直接使用rawfile目录下的文件(一般使用的是图片文件),并且需要包含文件后缀,此方式可以进行字符串拼接
- 其他可以看文档
测验
完成以上内容的学习时,可以尝试完成测试
功能模块:
- router.push:跳转到指定页面
- router.replace:使用页面替换当前页面,当前页面会被销毁
- router.back:返回到上一页,或返回到指定页面
- 可以传入一个参数:RouterOptions,该参数用于指定路由参数:跳转页面的url和传递的参数Params
- router.getParams:在跳转后的页面中获取传入的参数,此参数为Object类型,需要使用router.getParams()['key']来获取对应的值
进阶
自定义组件的生命周期
aboutToAppear
- 创建实例之后,build函数之前执行,可以在这里做一些数据的初始化操作
onPageShow
- 仅对页面组件生效(也就是@Entry修饰的组件)
- 每次显示时触发一次,可以做开屏定时器之类的操作
onBackPress
- 仅对页面组件生效(也就是@Entry修饰的组件)
- 用户点击手机上的返回按钮时触发,可以做一些提示性弹窗
onPageHide
- 仅对页面组件生效(也就是@Entry修饰的组件)
- 页面每隐藏时触发一次,暂没有合适的应用场景
aboutToDisappear
- 当前组件的实例被销毁之前执行,比如当前页被路由replace时
- 不能在这个时候修改状态变量
- 可以执行一些释放资源的操作
基础能力
- setTimeout:延时执行一个函数,仅执行一次,返回延时器的ID
- clearTimeout:传入上面的ID,可以关闭此延时器
- setInterval:周期性执行一个函数,一直重复执行,直到被清除,返回ID
- clearInterval:传入上面的ID,可以清除该周期性任务
参考资料:
本文是作者学习鸿蒙OS的过程记录,包括遇到的问题与吐槽,如设备适配、版本混乱和文档易用性。此外,文章还总结了ArkTS语法和HarmonyOS组件学习的重点,如类型、接口、类、函数、泛型以及组件的使用,如Flex、Column和Tab等。
2280

被折叠的 条评论
为什么被折叠?



