【Harmony OS 4.0】从零开始,快速上手

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 创建项目并初始化

  1. 项目 build init 时报错:request to http://registry.cnpmjs.org/pnpm failed, reason: getaddrinfo ENOTFOUND registry.cnpmjs.org
  2. 运行 npm install -g cnpm --registry=https://registry.npm.taobao.org,仍报错:request to https://registry.npm.taobao.org/cnpm failed, reason: certificate has expired. 显示证书过期。使用的是老版本的淘宝镜像,淘宝不在维护.
  3. 设置淘宝最新的镜像源. 运行 npm install -g cnpm --registry=https://registry.npmmirror.com. 成功

1.3 创建模拟器

  1. 创建模拟器前需要先申请参加模拟器Beta活动.
  2. 若提示该账号没有权限,请先点击"Submit the application form"完成权限申请.

1.4 了解项目目录:工程级目录 / 模块级目录

  1. 通过修改下面俩个文件路径设置模拟器打开的主页面
    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

  1. 安装 Nodejs,配置 Nodejs 的环境变量
  2. 安装 typescript,npm install -g typescript,tsc -v 查看版本号
  3. 编译 .ts 文件的命令:tsc 文件名
    编译 .js 文件的命令:node 文件名

2. TypeScript 快速入门

2.1 掌握 ts 常用基本类型

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 掌握条件语句

ts 复制代码
// 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 掌握循环语句

ts 复制代码
// 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 掌握函数

ts 复制代码
// 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 必须本地初始化。

ts 复制代码
// 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循环遍历的对象)

迭代器都是可迭代对象

可迭代对象是迭代器的一个子集

ts 复制代码
// 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

  1. 继承 TS 的所有特性,是 TS 的超集。
  2. ArkUI 是一套构建分布式应用界面的声明式UI开发框架,只能使用表达式。组件中不需要renturn。
  3. 装饰器:
    3.1 @Entry:标识这是个入口组件,且表示是一个独立page页面,通过router进行跳转。
    3.2 @Component:自定义组件,可进行页面渲染。
    3.3 struct TextExample{}:定义名为TextExample的结构体,代表这个组件
    3.4 build() {}:定义build方法构建UI,类似ReactNative中的render方法,生成节点树实例。
  4. 自定义组件:
  5. 容器组件均支持子组件配置,可以实现相对复杂的多级嵌套。
    Column, Row, Flex, Stack, Grid, List 等组件都是容器组件
  6. 如果组件支持子组件配置,则需在尾随闭包 "{...}" 中为组件添加子组件的UI描述
  7. UI描述:
  8. 系统组件:由ArkUI直接提供的组件。
  9. 自定义组件:由开发者定义的组件。
  10. 属性方法:
  11. 事件方法:

3.1 配置组件的事件方法

事件方法以 "." 链式调用的方式配置系统组件支持的事件,建议每个事件方法单独写一行。

ts 复制代码
// 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 资源引用类型

ts 复制代码
// @Builder 自定义构建函数时,用 :Resource 表示资源引用类型
@Builder function CreateIcon (icon: Resource): void {
  Column() {
    Image(icon).width(28).height(28).objectFit(ImageFit.Contain).margin(10)
  }
}

3.2.2 Length 长度类型,用于描述尺寸单位

ts 复制代码
@Prop widthValue: Length = 0

3.2.3 ResourceStr 字符串类型,用于描述字符串入参可以使用的类型

3.2.4 Padding 内边距类型,用于描述组件不同方向的内边距

3.2.5 Margin

4. 容器组件

4.1 Column

  1. 是沿垂直方向布局容器
  2. 支持很多通用属性,如alignItems,justifyContent,width,height,border等
ts 复制代码
// 常用属性
Column({space: 5}){} // 创建一个column容器组件,设置子元素间的垂直间距为5,纵向布局元素垂直方向间距为5.
.alignItems(HorizontalAlign.Start)
.justifyContent(FlexAlign.Center)

4.2 Row

  1. 是沿水平方向布局容器
  2. 支持很多通用属性,如alignItems,justifyContent,width,height,border等
ts 复制代码
// 常用属性
Row({space: 5}){} // 横向布局元素间距
.alignItems(VerticalAlign.Center)
.justifyContent(FlexAlign.Start)

4.3 List

  1. onReachEnd 事件:列表到底末尾位置时触发。
    List边缘效果为弹簧效果时,划动经过末尾位置时触发一次,回弹回末尾位置时再触发一次。

5. 基础组件

5.1 Text

  1. Text 组件是可以显示一段文本的组件。
ts 复制代码
// 常用属性:
.textAlign(TextAlign.Center)
.fontSize(14)
.fontColor()
.textOverflow({overflow: TextOverflow.Ellipsis}) // 设置文本溢出方式为省略号
.maxLines(1) // 设置最大行数为1
.border({width: 1})
.lineHeight(15) // 设置行高为20
.padding(10)

5.2 Image

ts 复制代码
// 引入项目本地 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

  1. 滚动条组件ScrollBar,用于配合可滚动组件使用,如List、Grid、Scroll

6. @Component 自定义组件

  1. @Component 装饰的UI单元,可以组合多个系统组件实现UI的复用。
  2. 由开发者定义的称为自定义组件
  3. 具有以下特点
    3.1 可组合:允许开发者组合使用系统组件,及其属性和方法。
    3.2 可重用:可以被其他组件重用,并作为不同的实例在不同的父组件或容器中使用。
    3.3 数据驱动UI更新,通过状态变量的改变,来驱动UI的刷新。

6.1 自定义组件通用样式

ts 复制代码
@Component
struct 自定义组件名 {
	build() {}
}

6.2 实现并引用自定义组件

ts 复制代码
// 自定义子组件
@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. 页面和自定义组件生命周期

  1. 自定义组件:@Component 装饰的UI单元,可以组合多个系统组件实现UI的复用。
    组件生命周期,即一般用@Component装饰的自定义组件的生命周期,提供以下生命周期接口:
    1.1 aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行。
    1.1 aboutToDisappear:在自定义组件即将析构销毁时执行。
  2. 页面:即应用的UI页面。可以由一个或多个自定义组件组成,@Entry装饰的自定义组件为页面的入口组件,即页面的根节点,一个页面有且仅能有一个@Entry。
    只有被@Entry装饰的组件才可以调用页面的生命周期。页面生命周期,即被@Entry装饰的组件生命周期,提供以下生命周期接口:
    2.1 onPageShow:页面每次显示时触发。
    2.2 onPageHide:页面每次隐藏时触发一次。
    2.3 onBackPress:当用户点击返回按钮时触发。
  3. 流程图如下

7.1 入口组件(页面)嵌套自定义组件的生命周期流程

  1. 页面组件aboutToAppear ---> 自定义组件aboutToAppear ---> 页面组件onPageShow(在页面组件点击删除自定义组件) --> 自定义组件aboutToDisappear
  2. 在页面组件跳转到另一个页面组件,页面组件onPageHide
  3. 在页面组件返回手机主屏,页面组件aboutToDisappear

8. @Builder 自定义构建函数

8.1 自定义组件内自定义构建函数

ts 复制代码
// 1. 定义语法
@Builder MyBuilderFunction() {}

// 2. 使用语法
this.MyBuilderFunction() {}

8.2 全局自定义构建函数

ts 复制代码
// 1. 定义语法
@Builder function MyGlobalBuilderFunction() {}

// 2. 使用语法
MyGlobalBuilderFunction() {}

8.3 参数传递规则

  1. 按引用传递参数
  2. 按值传递参数
    2.1 当传递的参数为状态变量时,状态变量的改变不会引起@Builder方法内的UI刷新。所以当使用状态变量的时候,推荐使用按引用传递
ts 复制代码
// 按引用传递参数
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. 状态管理

  1. 状态变量:被状态装饰器(@State) 装饰的变量叫状态变量,状态变量值的改变会引起UI的渲染更新。@State count: number = 0
  2. 常规变量:没有被状态装饰器修饰的变量,通常应用于辅助计算。它的改变永远不会引起UI的渲染更新。private/public increaseBy: number = 1
  3. 数据源/同步源:状态变量的原始来源,可以同步给不同的状态数据。通常意义为父组件传给子组件的数据。
  4. 命名参数机制:父组件通过指定参数传递给子组件的状态变量,为父子传递同步参数的主要手段
  5. 从父组件初始化:父组件使用命名参数机制,将指定参数传递给子组件。子组件初始化的默认值在有父组件传值的情况下,会被覆盖。
  6. 初始化子组件:父组件中状态变量可以传递给子组件,初始化子组件对应的状态变量
  7. 本地初始化:在变量声明的时候赋值,作为变量的默认值。

9.1 @State装饰器 - 组件内状态 - 必须本地初始化

  1. 与声明式范式中的其他被装饰变量一样,是私有的,只能从组件内部访问,在声明时必须指定其类型和本地初始化
  2. 与子组件中的@Prop,@Link、@ObjectLink装饰变量之间建立双向数据同步。
ts 复制代码
// 自定义类型
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装饰器 - 父子单向同步 - 允许本地初始化

  1. @Prop装饰变量时会进行深拷贝,在拷贝的过程中除了基本类型、Map、Set、Date、Array外,都会丢失类型。
  2. @Prop装饰器不能在@Entry装饰的自定义组件中使用。
  3. 被装饰变量的初始值,允许本地初始化。
ts 复制代码
// @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装饰器 - 父子双向同步 - 禁止本地初始化

  1. 父组件中@State, @StorageLink和@Link 和 子组件@Link可以建立双向数据同步。
  2. 被装饰变量的初始值,禁止本地初始化。

下面代码展示了class类对象类型、简单类型,俩种父子双向数据同步示例。

ts 复制代码
// @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 条件渲染

  1. 渲染控制的能力
  2. 修改条件分支的时候,会把原来的条件分支组件先删除,再重新创建一个条件分支组件。
ts 复制代码
// 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 循环渲染

  1. ForEach接口基于数组类型数据来进行循环渲染,需要与容器组件配合使用,且接口返回的组件应当是允许包含在ForEach父容器组件中的子组件。
    例如,ListItem组件要求ForEach的父容器组件必须为List组件。
  2. 数组类型数据
  3. 接受三个参数
  4. 和 if...else 不一样,会做二次渲染,可以做到单独修改某一个值。
ts 复制代码
// 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%')
  }
}
相关推荐
卿卿qing3 个月前
【Harmony OS 4.0】自定义组件 —— @Component 装饰器
自定义组件·harmony os 4.0
卿卿qing3 个月前
【Harmony OS 4.0】TypeScript 快速入门
typescript·harmony os 4.0
卿卿qing3 个月前
【Harmony OS 4.0】交互事件(手势事件)
harmony os 4.0