2019年8月份Harmony OS 1.0,
2020年9月份Harmony OS 2.0
2022年7月份Harmony OS 3.0
2023年3月份Harmony OS 4.0,不兼容 android app
1. 快速上手
1.1 下载并安装 DevEco Studio
1.2 创建项目并初始化
- 项目 build init 时报错:request to http://registry.cnpmjs.org/pnpm failed, reason: getaddrinfo ENOTFOUND registry.cnpmjs.org
- 运行 npm install -g cnpm --registry=https://registry.npm.taobao.org,仍报错:request to https://registry.npm.taobao.org/cnpm failed, reason: certificate has expired. 显示证书过期。使用的是老版本的淘宝镜像,淘宝不在维护.
- 设置淘宝最新的镜像源. 运行 npm install -g cnpm --registry=https://registry.npmmirror.com. 成功
1.3 创建模拟器
- 创建模拟器前需要先申请参加模拟器Beta活动.
- 若提示该账号没有权限,请先点击“Submit the application form”完成权限申请.
1.4 了解项目目录:工程级目录 / 模块级目录
- 通过修改下面俩个文件路径设置模拟器打开的主页面
1.1 src/main/ets/entryability/entryability.ts ⇒ windowStage.loadContent(‘test_pages/test2’)
1.2 resources/base/profile/main_pages.json ⇒ { “src”: [‘test_pages/test2’] },配置router路径
1.5 DevEco Studio 配置 TypeScript
- 安装 Nodejs,配置 Nodejs 的环境变量
- 安装 typescript,npm install -g typescript,tsc -v 查看版本号
- 编译 .ts 文件的命令:tsc 文件名
编译 .js 文件的命令:node 文件名
2. TypeScript 快速入门
2.1 掌握 ts 常用基本类型
// 1. boolean 类型
let isOff = true
let isOn = false
// 2. 数字类型
let a: Number/number = 2
// 3. 字符串类型
let aStr:string = "hello"
// 4. 俩种数组类型
let arr1:number[] = [1, 3, 5]
let arr2: Array<string> = ["hello", "world"]
// 4.1 回顾splice用法,改变原数组,返回修改后的数组
let arr3 = [1, 3, 5, 7]
arr3.splice(2, 0, 3) // 把值3添加到下标2的地方
console.log(arr3) // [1, 3, 3, 5, 7]
arr3.splice(4, 1) // 删除下标4的值
console.log(arr3) // [1, 3, 3, 5]
// 5. 定义一个元组类型 ==> 允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。
let arr4: [string, number]
arr4 = ['hello', 100]
arr4 = [100, 'hello'] // error demo
arr4 = ['hello', 100, 200] // error demo
// 6. 定义枚举 enum ==> 是对javascript标准数据类型的一个补充,可以为一组数值赋予友好的名字。
enum Color { red, green, blue }
let c: Color = Color.green
// 7. 定义未知类型 any/Unknown ==> 不清楚类型的变量,不希望类型检查器对这些值进行检查,直接通过编译阶段的检查。
let noSure: unknown/any
noSure = 4 // 这里4可以是任意值
// 8. viod ==> 函数没有返回值
function myfunc(): void {
// 这里不能有return
}
// 9. Null ==> 空值,没有给它分配内存,内存中没有变量;
// undefined ==> 未定义的变量,有这个变量,但是没有定义
// 10. 联合类型
let a: string | number
a = 'hello'
a = 100
2.2 掌握条件语句
// 1. if
// 2. if...else
// 3. if...else if...else
// 4. switch...case,用来做多条件的等于判断
let gender: string = 'mail'
switch (gender) {
case 'mail': {
console.log('男')
break; // 满足条件就终止判断
}
case 'femail': {
console.log('女')
break;
}
default: { // 上面所有case条件都不满足,就会进入default
console.log('不能确定性别')
break;
}
}
2.3 掌握循环语句
// 1.
let i: numebr;
for(i = 1;i <= 10;i++) {
console.log(i)
}
// 2. 只能针对集合或者是列表
let j: any
// let j: unkonwn
let nums: Array<number> = [12, 23, 56, 32]
for (j in nums) {
console.log(nums[j])
}
2.4 掌握函数
// 1. 有名函数
function add(x: number, y: number): void{}
add(10, 20)
// 2. 匿名函数
let add_Func = function(x: number, y: number): number{
return x + y
}
add_Func(10, 20)
// 3. 可选参数的函数
function fn(x: number, name?: string) :void {}
fn(10)
// 4. 剩余参数 ==> 个数不限的可选参数
function fn(x: number, ...other: Array<any>) :void {}
fn(10, 'hello', 30)
// 5. 箭头函数 ==> 定义匿名函数的简写语法
let fn = (x: number, y: number) => {}
2.5 掌握类对象 ==> public(公共的,在任何地方都一用),private(私有的,只能在类里面用), protected(在一个包里面可以用)
2.5.1 在 ArkTs 中,private 好处是类对象外的代码无法访问到,不允许别人在类外面随意改动它
2.5.2 在 ArkTs 中,private 必须本地初始化。
// 1. 定义一个Person类
class Person {
// 私有属性
private name: string
private age: number
// 定义构造函数
constructor(name: string, age: number){
this.name = name
this.age = age
}
// 公共成员函数
public getPersonInfo() :string {
return `我的名字是${this.name},年龄是${this.age}`
}
}
let p = new Person('张三', 18)
p.getPersonInfo()
// 2. 继承 extends
// 定义一个Employee类继承Person
class Employee extends Person {
private department: string;
constructor(name: string, age: number, department: string){
// 调用父类的构造函数
super(name, age)
this.department = department
}
public getEmployeeInfo() :string {
return this.getPersonInfo() + `,我的工作部门是${this.department}`
}
}
let emp = new Employee('王五', 26, '行政部')
emp.getEmployeeInfo()
emp.getPersonInfo()
2.6 掌握可迭代对象 ==> 当一个对象实现了 Symbol.iterator 属性时,它是可迭代的。一些内置的类型如 Array, Map, Set, String, Int32Array, Uint32Array 等都具有可迭代性。(可以通过for…of循环遍历的对象)
迭代器都是可迭代对象
可迭代对象是迭代器的一个子集
// 1. for..of 语句,针对可迭代对象才能使用,i 代表其中元素,不指下标。
let str: string = 'sdfs'
for(let i of str) {
console.log(i) // s d f s
}
// 2. map类型,每个元素都是由俩个组成:key,value
let map1 = new Map<string, number>()
map1.set('a', 111)
map1.set('b', 222)
map1.set('c', 333)
for(let j of map1) {
console.log(j) // ['a', 111] ['b', 222] ['c', 333]
console.log(j[1]) // 111 222 333
}
2.7 模块 ==> 前端模块化
3. ArkTs/ArkUI
- 继承 TS 的所有特性,是 TS 的超集。
- ArkUI 是一套构建分布式应用界面的声明式UI开发框架,只能使用表达式。组件中不需要renturn。
- 装饰器:
3.1 @Entry:标识这是个入口组件,且表示是一个独立page页面,通过router进行跳转。
3.2 @Component:自定义组件,可进行页面渲染。
3.3 struct TextExample{}:定义名为TextExample的结构体,代表这个组件
3.4 build() {}:定义build方法构建UI,类似ReactNative中的render方法,生成节点树实例。- 自定义组件:
- 容器组件均支持子组件配置,可以实现相对复杂的多级嵌套。
Column, Row, Flex, Stack, Grid, List 等组件都是容器组件- 如果组件支持子组件配置,则需在尾随闭包 “{…}” 中为组件添加子组件的UI描述
- UI描述:
- 系统组件:由ArkUI直接提供的组件。
- 自定义组件:由开发者定义的组件。
- 属性方法:
- 事件方法:
3.1 配置组件的事件方法
事件方法以 “.” 链式调用的方式配置系统组件支持的事件,建议每个事件方法单独写一行。
// 1. 使用箭头函数配置组件的事件方法
Button('Click me')
.onClick(() => {})
// 2. 使用匿名函数表达式配置组件的事件方法,要求使用bind,以确保函数题中的this指向当前组件
Button('add counter')
.onClick(function(){}.bind(this))
// 3. 使用组件的成员函数配置组件的事件方法,⚠️组件的成员函数只能在事件方法上面定义
myClickHandler(): void {}
Button('add counter')
.onClick(this.myClickHandler.bind(this))
3.2 类型定义
3.2.1 Resource 资源引用类型
// @Builder 自定义构建函数时,用 :Resource 表示资源引用类型
@Builder function CreateIcon (icon: Resource): void {
Column() {
Image(icon).width(28).height(28).objectFit(ImageFit.Contain).margin(10)
}
}
3.2.2 Length 长度类型,用于描述尺寸单位
@Prop widthValue: Length = 0
3.2.3 ResourceStr 字符串类型,用于描述字符串入参可以使用的类型
3.2.4 Padding 内边距类型,用于描述组件不同方向的内边距
3.2.5 Margin
4. 容器组件
4.1 Column
- 是沿垂直方向布局容器
- 支持很多通用属性,如alignItems,justifyContent,width,height,border等
// 常用属性
Column({space: 5}){} // 创建一个column容器组件,设置子元素间的垂直间距为5,纵向布局元素垂直方向间距为5.
.alignItems(HorizontalAlign.Start)
.justifyContent(FlexAlign.Center)
4.2 Row
- 是沿水平方向布局容器
- 支持很多通用属性,如alignItems,justifyContent,width,height,border等
// 常用属性
Row({space: 5}){} // 横向布局元素间距
.alignItems(VerticalAlign.Center)
.justifyContent(FlexAlign.Start)
4.3 List
- onReachEnd 事件:列表到底末尾位置时触发。
List边缘效果为弹簧效果时,划动经过末尾位置时触发一次,回弹回末尾位置时再触发一次。
5. 基础组件
5.1 Text
- Text 组件是可以显示一段文本的组件。
// 常用属性:
.textAlign(TextAlign.Center)
.fontSize(14)
.fontColor()
.textOverflow({overflow: TextOverflow.Ellipsis}) // 设置文本溢出方式为省略号
.maxLines(1) // 设置最大行数为1
.border({width: 1})
.lineHeight(15) // 设置行高为20
.padding(10)
5.2 Image
// 引入项目本地 resources/base/media 路径下的图片
Image($r('app.media.ic_default'))
// @Builder 自定义构建函数时,用 :Resource 表示资源引用类型
@Builder function CreateIcon (icon: Resource): void {
Column() {
Image(icon).width(28).height(28).objectFit(ImageFit.Contain).margin(10)
}
}
5.3 ScrollBar
- 滚动条组件ScrollBar,用于配合可滚动组件使用,如List、Grid、Scroll
6. @Component 自定义组件
- @Component 装饰的UI单元,可以组合多个系统组件实现UI的复用。
- 由开发者定义的称为自定义组件
- 具有以下特点
3.1 可组合:允许开发者组合使用系统组件,及其属性和方法。
3.2 可重用:可以被其他组件重用,并作为不同的实例在不同的父组件或容器中使用。
3.3 数据驱动UI更新,通过状态变量的改变,来驱动UI的刷新。
6.1 自定义组件通用样式
@Component
struct 自定义组件名 {
build() {}
}
6.2 实现并引用自定义组件
// 自定义子组件
@Component
struct MyHelloComponent {
@State username: string = '🧚'
build(){
Column() {
Text(`hello ${this.username}`)
.fontSize(16)
.fontColor(Color.Grey)
.height(15)
.onClick(() => {
this.username = '七喜'
})
}
}
}
// 父组件
@Entry
@Component
struct UITest3 {
build() {
Column({space: 10}) {
Text('主界面')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor(Color.Black)
MyHelloComponent({username: 'world'}) // 引用自定义组件
Divider()
MyHelloComponent({username: 'SZ'}) // 引用自定义组件
}
}
}
7. 页面和自定义组件生命周期
- 自定义组件:@Component 装饰的UI单元,可以组合多个系统组件实现UI的复用。
组件生命周期,即一般用@Component装饰的自定义组件的生命周期,提供以下生命周期接口:
1.1 aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行。
1.1 aboutToDisappear:在自定义组件即将析构销毁时执行。- 页面:即应用的UI页面。可以由一个或多个自定义组件组成,@Entry装饰的自定义组件为页面的入口组件,即页面的根节点,一个页面有且仅能有一个@Entry。
只有被@Entry装饰的组件才可以调用页面的生命周期。页面生命周期,即被@Entry装饰的组件生命周期,提供以下生命周期接口:
2.1 onPageShow:页面每次显示时触发。
2.2 onPageHide:页面每次隐藏时触发一次。
2.3 onBackPress:当用户点击返回按钮时触发。- 流程图如下
7.1 入口组件(页面)嵌套自定义组件的生命周期流程
- 页面组件aboutToAppear —> 自定义组件aboutToAppear —> 页面组件onPageShow(在页面组件点击删除自定义组件) --> 自定义组件aboutToDisappear
- 在页面组件跳转到另一个页面组件,页面组件onPageHide
- 在页面组件返回手机主屏,页面组件aboutToDisappear
8. @Builder 自定义构建函数
8.1 自定义组件内自定义构建函数
// 1. 定义语法
@Builder MyBuilderFunction() {}
// 2. 使用语法
this.MyBuilderFunction() {}
8.2 全局自定义构建函数
// 1. 定义语法
@Builder function MyGlobalBuilderFunction() {}
// 2. 使用语法
MyGlobalBuilderFunction() {}
8.3 参数传递规则
- 按引用传递参数
- 按值传递参数
2.1 当传递的参数为状态变量时,状态变量的改变不会引起@Builder方法内的UI刷新。所以当使用状态变量的时候,推荐使用按引用传递
// 按引用传递参数
class obj {
username: string = ''
}
@Builder function MyBuilder($$: obj) { // 参数需要直接传入对象字面量才会按引用传递该参数,其余传递方式均为按值传递。
Column() {
Text(`hello ${$$.username}`)
.fontSize(18)
.fontColor(Color.Grey)
}
}
@Entry
@Component
struct UITest4 {
@State person_name: string = '花花' // 当传递的参数为状态变量时,状态变量的改变才会引起@Builder方法内的UI刷新
build() {
Column() {
MyBuilder({username: this.person_name})
Button('点击修改')
.margin({top: 8})
.onClick(() => {
this.person_name = '七喜'
})
}
}
}
9. 状态管理
- 状态变量:被状态装饰器(@State) 装饰的变量叫状态变量,状态变量值的改变会引起UI的渲染更新。@State count: number = 0
- 常规变量:没有被状态装饰器修饰的变量,通常应用于辅助计算。它的改变永远不会引起UI的渲染更新。private/public increaseBy: number = 1
- 数据源/同步源:状态变量的原始来源,可以同步给不同的状态数据。通常意义为父组件传给子组件的数据。
- 命名参数机制:父组件通过指定参数传递给子组件的状态变量,为父子传递同步参数的主要手段
- 从父组件初始化:父组件使用命名参数机制,将指定参数传递给子组件。子组件初始化的默认值在有父组件传值的情况下,会被覆盖。
- 初始化子组件:父组件中状态变量可以传递给子组件,初始化子组件对应的状态变量
- 本地初始化:在变量声明的时候赋值,作为变量的默认值。
9.1 @State装饰器 - 组件内状态 - 必须本地初始化
- 与声明式范式中的其他被装饰变量一样,是私有的,只能从组件内部访问,在声明时必须指定其类型和本地初始化
- 与子组件中的@Prop,@Link、@ObjectLink装饰变量之间建立双向数据同步。
// 自定义类型
class Person {
public name: string
public age: number
constructor(xName: string, xAge: number) {
this.name = xName
this.age = xAge
}
}
// 自定义子组件
@Component
struct MyChild {
@State person: Person = new Person('魔鬼', 18) // 指定自定义类型,初始化值
private increase: number = 1
build() {
Column({space: 3}) {
Text(`${this.person.name}、年龄${this.person.age}`).fontSize(18).fontWeight(500).height(60)
Divider()
Button(`修改姓名`).onClick(() => {
this.person.name = this.person.name === '魔鬼' ? '天使': '魔鬼'
})
.width(200)
.height(60)
Button('修改年龄').onClick(() => {
this.person.age += this.increase
})
.width(200)
.height(60)
}
// .backgroundColor('#CCBBFF')
}
}
// 父组件
@Entry
@Component
struct UITest5 {
build() {
Column({space: 30}) {
MyChild({person: new Person('天使', 2), increase: 1})
MyChild({increase: 2})
MyChild()
}
}
}
9.2 @Prop装饰器 - 父子单向同步 - 允许本地初始化
- @Prop装饰变量时会进行深拷贝,在拷贝的过程中除了基本类型、Map、Set、Date、Array外,都会丢失类型。
- @Prop装饰器不能在@Entry装饰的自定义组件中使用。
- 被装饰变量的初始值,允许本地初始化。
// @Prop装饰器 - 父子单向同步
// 子组件
@Component
struct MyChild {
@Prop age: number = 9 // 被装饰变量的初始值,允许本地初始化。当没有初始值时,页面显示undefined
private increase: number = 1
build() {
Column(){
if (this.age >= 18) {
Text(`子组件年龄${this.age}`).height(60)
} else {
Text(`未成年${this.age}`).height(60)
}
Button('减少子组件年龄').onClick(() => {
this.age -= this.increase
})
}
}
}
// 父组件
@Entry
@Component
struct UITest6{
@State init_age: number = 14
build() {
Column() {
Text(`父组件年龄${this.init_age}`).height(60)
Button('增加父组件年龄').onClick(() => {
this.init_age += 1
})
Divider()
MyChild({age: this.init_age, increase: 2})
Divider()
MyChild()
}
}
}
9.3 @Link装饰器 - 父子双向同步 - 禁止本地初始化
- 父组件中@State, @StorageLink和@Link 和 子组件@Link可以建立双向数据同步。
- 被装饰变量的初始值,禁止本地初始化。
下面代码展示了class类对象类型、简单类型,俩种父子双向数据同步示例。
// @Link装饰器 - 父子双向同步。class类对象类型和简单类型俩种父子双向同步
// 自定义class类对象类型
class ButtonState {
value: string
width: number
constructor(value: string, width: number) {
this.value = value
this.width = width
}
}
// 一号子组件 - class类对象类型
@Component
struct MyChildButton01 {
// class类对象类型
@Link buttonState: ButtonState
build() {
Column() {
Button(`${this.buttonState.value}${this.buttonState.width}`)
.width(this.buttonState.width)
.height(50)
.backgroundColor('#B088FF')
.fontColor('#FFFFFF,90%')
.onClick(() => {
if (this.buttonState.width < 400) {
this.buttonState.width += 50
} else {
this.buttonState = new ButtonState('一号本地', 100)
}
})
}
}
}
// 二号子组件 - 简单类型
@Component
struct MyChildButton02 {
// 简单类型
@Link buttonValue: string
@Link buttonWidth: number
build() {
Column() {
Button(`${this.buttonValue}${this.buttonWidth}`)
.width(this.buttonWidth)
.height(50)
.backgroundColor('#FF7744')
.fontColor('#FFFFFF,90%')
.onClick(() => {
this.buttonValue = this.buttonWidth < 400 ? this.buttonValue : '二号本地'
this.buttonWidth = this.buttonWidth < 400 ? this.buttonWidth + 50 : 100
})
}
}
}
// 父组件
@Entry
@Component
struct UITest6 {
// class类对象类型
@State parentButton: ButtonState = new ButtonState('一号子级', 100)
// 简单类型
@State parentValue: string = '二号子级'
@State parentWidth: number = 100
build() {
Column({space: 10}) {
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Start, alignItems: ItemAlign.Start }) {
// class类对象类型从父组件@State向子组件@Link数据同步
Button(`父组件中修改${this.parentButton.value}:${this.parentButton.width}`).onClick(() => {
this.parentButton.width = this.parentButton.width < 400 ? this.parentButton.width + 50 : 100
})
// 简单类型从父组件@State向子组件@Link数据同步
Button(`父组件中修改${this.parentValue}:${this.parentWidth}`).onClick(() => {
this.parentWidth = this.parentWidth < 400 ? this.parentWidth + 50 : 100
})
// class类对象类型初始化@Link
MyChildButton01({ buttonState: $parentButton })
// 简单类型初始化@Link
MyChildButton02({ buttonValue: $parentValue, buttonWidth: $parentWidth })
}
}
}
}
10. if/else 条件渲染
- 渲染控制的能力
- 修改条件分支的时候,会把原来的条件分支组件先删除,再重新创建一个条件分支组件。
// if/else 条件渲染
// 子组件
@Component
struct MyChild {
@Link count: number // @Link装饰器 - 父子双向同步 - 禁止本地初始化
@Prop label: string // @Prop装饰器 - 父子单向同步 - 允许本地初始化
build() {
Row() {
Flex({justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center}) {
Text(`${this.label}`)
Button(`计数器${this.count}`).onClick(() => {
this.count += 1
})
}
// .backgroundColor('#B94FFF')
}
}
}
// 父组件
@Entry
@Component
struct UITest6 {
@State flag: boolean = true // @State装饰器 - 组件内状态 - 必须本地初始化
@State parentCount: number = 0
build() {
Column() {
if (this.flag) {
MyChild({ count: $parentCount, label: '真真' })
} else {
MyChild({ count: $parentCount, label: '假假' })
}
Button(`是否真假:${this.flag}`).onClick(() => {
this.flag = !this.flag
}).margin(10)
}
}
}
11. ForEach 循环渲染
- ForEach接口基于数组类型数据来进行循环渲染,需要与容器组件配合使用,且接口返回的组件应当是允许包含在ForEach父容器组件中的子组件。
例如,ListItem组件要求ForEach的父容器组件必须为List组件。- 数组类型数据
- 接受三个参数
- 和 if…else 不一样,会做二次渲染,可以做到单独修改某一个值。
// ForEach 循环渲染
// 子组件
@Component
struct MyChild {
@Prop label: string
build() {
Column() {
Text(this.label).fontSize(24).fontColor("#9955FF")
}
}
}
// 父组件
@Entry
@Component
struct UITest7 {
@State my_array: Array<string> = ['one', 'two', 'three']
build() {
Column({space: 15}) {
ForEach(this.my_array, (item: string, index: number) => { // ForEach接口基于数组类型数据来进行循环渲染,需要与容器组件配合使用。
MyChild({label: item}) // 且接口返回的组件应当是允许包含在ForEach父容器组件中的子组件。
})
Button('修改two').onClick(() => {
this.my_array[1] = 'new_two' // 和 if..else 不一样,会做二次渲染,可以做到单独修改某一个值。
})
}
.justifyContent(FlexAlign.Center)
.height("100%")
.width('100%')
}
}