Skip to content

0x011-鸿蒙应用开发笔记1-基础知识

页面跳转

跳转

跳转到第二个页面:

js
import { router } from '@kit.ArkUI'

// ....
Button() {  
  Text("Next")  
}  
.type(ButtonType.Normal)  
.onClick(event => {  
  console.log('button click');  
  router.pushUrl({  
    url: 'pages/Second'  
  }).then(() => {  
    console.info('Succeeded in jumping to the second page.')  
  }).catch((err: BusinessError) => {  
    console.error(`Failed to jump to the second page. Code is ${err.code}, message is ${err.message}`)  
  })  
})
import { router } from '@kit.ArkUI'

// ....
Button() {  
  Text("Next")  
}  
.type(ButtonType.Normal)  
.onClick(event => {  
  console.log('button click');  
  router.pushUrl({  
    url: 'pages/Second'  
  }).then(() => {  
    console.info('Succeeded in jumping to the second page.')  
  }).catch((err: BusinessError) => {  
    console.error(`Failed to jump to the second page. Code is ${err.code}, message is ${err.message}`)  
  })  
})

注意第二个页面需要注册路由(entry > src > main > resources > base > profile):

页面传参

页面返回

js
import { router } from '@kit.ArkUI'

router.back();
import { router } from '@kit.ArkUI'

router.back();

页面参数回调

可以通过函数闭包的方式处理。

TODO

生命周期

UIAbility的生命周期

UIAbility的生命周期包括Create、Foreground、Background、Destroy四个状态。

@Component组件的生命周期

  • aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行。
  • onDidBuild:组件build()函数执行完成之后回调该接口,不建议在onDidBuild函数中更改状态变量、使用animateTo等功能,这可能会导致不稳定的UI表现。
  • aboutToDisappear:aboutToDisappear函数在自定义组件析构销毁之前执行。不允许在aboutToDisappear函数中改变状态变量,特别是@Link变量的修改可能会导致应用程序行为不稳定。另外不建议在生命周期aboutToDisappear内使用async await。

@Entry组件/页面的生命周期

  • onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景。
  • onPageHide:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。
  • onBackPress:当用户点击返回按钮时触发。

无感监听页面路由

使用observer监听。

如何自定义组件

  • 自定义组件的成员函数为私有的,且不建议声明成静态函数
  • 状态管理相关文档:状态管理概述

父组件引用子组件

虽然声明的是private,但仍然支持传递参数。

示例代码:

js
@Entry
@Component
struct Index {
  @State message: string = 'Hello World';
  @State color: Color = Color.Red

  build() {
    Row() {
      SubComponent({ color: this.color, text: 'this is a text' })     
    }
  }
}

@Component
struct SubComponent {
  private color: Color = Color.Green
  private text = ''

  build() {
    Text(this.text)
      .fontSize(40)
      .foregroundColor(this.color)
  }
}
@Entry
@Component
struct Index {
  @State message: string = 'Hello World';
  @State color: Color = Color.Red

  build() {
    Row() {
      SubComponent({ color: this.color, text: 'this is a text' })     
    }
  }
}

@Component
struct SubComponent {
  private color: Color = Color.Green
  private text = ''

  build() {
    Text(this.text)
      .fontSize(40)
      .foregroundColor(this.color)
  }
}

父组件更新子组件

@Prop装饰的变量可以和父组件建立单向同步关系,@Prop装饰的变量是可变的,但修改不会同步回父组件。

js
@Entry
@Component
struct Index {
  @State message: string = 'Hello World';
  @State color: Color = Color.Red

  build() {
    Row() {
      SubComponent({ color: this.color, text: 'this is a text' })
      Button() {
        Text('yellow')
          .foregroundColor(Color.White)
          .padding(4)
      }
      .onClick(() => {
        this.color = Color.Yellow
      })
    }
  }
}

@Component
struct SubComponent {
  @Prop color: Color = Color.Green
  private text = ''

  build() {
    Text(this.text)
      .fontSize(40)
      .foregroundColor(this.color)
      .onClick(() => {
        // 不会更新到父组件
        this.color = Color.Orange
      })
  }
}
@Entry
@Component
struct Index {
  @State message: string = 'Hello World';
  @State color: Color = Color.Red

  build() {
    Row() {
      SubComponent({ color: this.color, text: 'this is a text' })
      Button() {
        Text('yellow')
          .foregroundColor(Color.White)
          .padding(4)
      }
      .onClick(() => {
        this.color = Color.Yellow
      })
    }
  }
}

@Component
struct SubComponent {
  @Prop color: Color = Color.Green
  private text = ''

  build() {
    Text(this.text)
      .fontSize(40)
      .foregroundColor(this.color)
      .onClick(() => {
        // 不会更新到父组件
        this.color = Color.Orange
      })
  }
}

双向绑定

参照 #link-状态管理

父组件调用子组件的方法

// 参考: https://developer.huawei.com/consumer/cn/forum/topic/0202140470508418066?fid=0102683795438680754

声明:

ts
export class ComponentRef<T> {  
  value ?: T  
  
  export(ref: T) {  
    this.value = ref  
  }  
  
  static useRef<T>() {  
    return new ComponentRef<T>()  
  }  
}  

export type CommonRefInterface = Record<string, Function>; // 示例,未使用
export interface WebComponentRef {  
  reloadWebView : Function  
}
export class ComponentRef<T> {  
  value ?: T  
  
  export(ref: T) {  
    this.value = ref  
  }  
  
  static useRef<T>() {  
    return new ComponentRef<T>()  
  }  
}  

export type CommonRefInterface = Record<string, Function>; // 示例,未使用
export interface WebComponentRef {  
  reloadWebView : Function  
}

父组件:

ts
  childrenRef = ComponentRef.useRef<WebComponentRef>();
  build() {
	childComponent({ url: this.homeUrl, title: this.h5Title, childrenRef: this.childrenRef })
  }

  aboutToAppear(): void {
    this.eventHub.on(EventTypeEnum.RELOAD_HOME, async () => {
      this.childrenRef.value?.reloadWebView()      
    })
  }
  childrenRef = ComponentRef.useRef<WebComponentRef>();
  build() {
	childComponent({ url: this.homeUrl, title: this.h5Title, childrenRef: this.childrenRef })
  }

  aboutToAppear(): void {
    this.eventHub.on(EventTypeEnum.RELOAD_HOME, async () => {
      this.childrenRef.value?.reloadWebView()      
    })
  }

子组件:

ts
childrenRef: ComponentRef<WebComponentRef>| null = null

async aboutToAppear() {
	this.childrenRef?.export({
	  reloadWebView: () => {	     
		// 子组件需要调用的方法
	  }
	})
}
childrenRef: ComponentRef<WebComponentRef>| null = null

async aboutToAppear() {
	this.childrenRef?.export({
	  reloadWebView: () => {	     
		// 子组件需要调用的方法
	  }
	})
}

修饰器说明

@Entry

入口组件,一个文件只能有一个Entry

说明
从API version 9开始,该装饰器支持在ArkTS卡片中使用。
从API version 10开始,@Entry可以接受一个可选的LocalStorage的参数或者一个可选的EntryOptions参数。
从API version 11开始,该装饰器支持在元服务中使用。

参数 routeName

可以用于Router可以通过命名路由的方式跳转。 可参考文档: Router切换Navigation-设置组件导航和页面路由

参数 storage

页面级的UI状态存储。

参数 useSharedStorage

是否使用LocalStorage.getShared()接口返回的LocalStorage实例对象,默认值false。

@Component

自定义组件。 只能修饰struct关键字声明的数据结构。

  • freezeWhenInactive 是否开启组件冻结。自定义组件处于非激活状态时,状态变量将不响应更新,即@Watch不会调用,状态变量关联的节点不会刷新。

@Reusable

让自定义的组件具备可复用能力。

@State 状态管理

表示组件中的状态变量,状态改变时触发ui刷新。

@State装饰的变量,与声明式范式中的其他被装饰变量一样,是私有的,只能从组件内部访问,在声明时必须指定其类型和本地初始化。初始化也可选择使用命名参数机制从父组件完成初始化。
示例代码见父组件更新子组件

支持修饰: 简单类型、class类型(例如修饰一个viewModel)、map类型。

参考文档:
@State装饰器

@Prop 状态管理

@Prop装饰的变量可以和父组件建立单向的同步关系。@Prop装饰的变量是可变的,但是变化不会同步回其父组件。

  • 传递的时候数据会进行深拷贝
  • 不支持修饰any,支持undefined和null

用于双向同步。

  • 父组件用 @State,@StorageLink和@Link修饰,子组件用 @Link可以建立双向数据同步
  • 不要初始化@Link修饰的内容
js
@Entry
@Component
struct Index {
  @State color: Color = Color.Red 
  build() {
    Row() {
      SubComponent({ color: this.color, text: 'this is a text' })
      Button() {
        Text(this.color.toString())
          .foregroundColor(Color.White)
          .padding(4)
      }
      .onClick(() => {
        this.color = Color.Yellow
      })
    }
  }
}

@Component
struct SubComponent {
  @Link color: Color  
  private text = ''
  build() {
    Text(this.text)
      .fontSize(40)
      .foregroundColor(this.color)
      .onClick(() => {
        // 支持更新到父组件 
        this.color = Color.Orange 
      })
  }
}
@Entry
@Component
struct Index {
  @State color: Color = Color.Red 
  build() {
    Row() {
      SubComponent({ color: this.color, text: 'this is a text' })
      Button() {
        Text(this.color.toString())
          .foregroundColor(Color.White)
          .padding(4)
      }
      .onClick(() => {
        this.color = Color.Yellow
      })
    }
  }
}

@Component
struct SubComponent {
  @Link color: Color  
  private text = ''
  build() {
    Text(this.text)
      .fontSize(40)
      .foregroundColor(this.color)
      .onClick(() => {
        // 支持更新到父组件 
        this.color = Color.Orange 
      })
  }
}

@Provide/@Consume 状态管理

@Provide和@Consume,应用于与后代组件的双向数据同步。 有点类似Vue中Provide/Inject。

  • @Provide和@Consume可以通过相同的变量名或者相同的变量别名绑定,建议类型相同,否则会发生类型隐式转换,从而导致应用行为异常。
  • @Provide 被装饰变量的初始值必须指定
  • @Provide 被装饰变量的初始值禁止初始化
js
// 通过相同的变量名绑定
@Provide a: number = 0;
@Consume a: number;

// 通过相同的变量别名绑定
@Provide('a') b: number = 0;
@Consume('a') c: number;
// 通过相同的变量名绑定
@Provide a: number = 0;
@Consume a: number;

// 通过相同的变量别名绑定
@Provide('a') b: number = 0;
@Consume('a') c: number;

弥补深度嵌套场景的数据监听问题。

  • 使用@Observed装饰class会改变class原始的原型链,@Observed和其他类装饰器装饰同一个class可能会带来问题。
  • @ObjectLink装饰器不能在@Entry装饰的自定义组件中使用。
  • @ObjectLink装饰的变量不能被赋值(只有成员变量可以赋值),如果要使用赋值操作,请使用@Prop

@Watch

@Builder

修饰于函数,用于自定义构建UI。

例如:

js
class Tmp {
  paramA1: string = ''
}

@Builder function overBuilder(params: Tmp) {
  Row() {
    Text(`UseStateVarByReference: ${params.paramA1} `)
  }
}
class Tmp {
  paramA1: string = ''
}

@Builder function overBuilder(params: Tmp) {
  Row() {
    Text(`UseStateVarByReference: ${params.paramA1} `)
  }
}

按引用传递参数时,如果在@Builder方法内调用自定义组件,ArkUI提供$$作为按引用传递参数的范式。

js
class Tmp {
  paramA1: string = ''
}

@Builder function overBuilder($$: Tmp) {
  Row() {
    Column() {
      Text(`overBuilder===${$$.paramA1}`)
      HelloComponent({message: $$.paramA1})
    }
  }
}

@Component
struct HelloComponent {
  @Prop message: string;
  build() {
    Row() {
      Text(`HelloComponent===${this.message}`)
    }
  }
}

/// 父组件调用: 
@State label: string = 'Hello';
overBuilder({paramA1: this.label})
class Tmp {
  paramA1: string = ''
}

@Builder function overBuilder($$: Tmp) {
  Row() {
    Column() {
      Text(`overBuilder===${$$.paramA1}`)
      HelloComponent({message: $$.paramA1})
    }
  }
}

@Component
struct HelloComponent {
  @Prop message: string;
  build() {
    Row() {
      Text(`HelloComponent===${this.message}`)
    }
  }
}

/// 父组件调用: 
@State label: string = 'Hello';
overBuilder({paramA1: this.label})

@Preview

增加以支持单独预览单个小部件

Q&A

console 从哪里看

稍微有点隐蔽,需要点击底部Log标签查看。

官方有哪些示例

如何进行接口调用

viewModel的示例

@State装饰器:组件内状态-管理组件拥有的状态-状态管理(V1稳定版)-状态管理-学习ArkTS语言-基础入门 - 华为HarmonyOS开发者

function中的this指向问题

稍微注意下this的指向问题,函数中this只在乎谁调用的它。

js
// 方案1:推荐
Button('Click me')
  .onClick(() => {
    this.myText = 'ArkUI';
  })

// 方案2:可用,不推荐
myClickHandler(): void {
  this.counter += 2;
}
...
Button('add counter')
  .onClick(this.myClickHandler.bind(this))


// 方案3: 可用
fn = () => {
  console.info(`counter: ${this.counter}`)
  this.counter++
}
...
Button('add counter')
  .onClick(this.fn)
// 方案1:推荐
Button('Click me')
  .onClick(() => {
    this.myText = 'ArkUI';
  })

// 方案2:可用,不推荐
myClickHandler(): void {
  this.counter += 2;
}
...
Button('add counter')
  .onClick(this.myClickHandler.bind(this))


// 方案3: 可用
fn = () => {
  console.info(`counter: ${this.counter}`)
  this.counter++
}
...
Button('add counter')
  .onClick(this.fn)

Foreach渲染注意事项

foreach生成的键值用来标识唯一的对象。

  • ForEach提供了一个名为keyGenerator的参数,这是一个函数,开发者可以通过它自定义键值的生成规则。
  • 如果开发者没有定义keyGenerator函数,则ArkUI框架会使用默认的键值生成函数,即:
js
(item: Object, index: number) => 
	{ return index + '__' + JSON.stringify(item); 
}
(item: Object, index: number) => 
	{ return index + '__' + JSON.stringify(item); 
}

例如

js
@Component
struct SubComponent {
  private text = ''
  number: number[] = [1, 2, 3]

  build() {
    Row() {
      ForEach(this.number, (item: number) => {
        Text(item + '')
      }, (item: number) => item + '')
    }
  }
}
@Component
struct SubComponent {
  private text = ''
  number: number[] = [1, 2, 3]

  build() {
    Row() {
      ForEach(this.number, (item: number) => {
        Text(item + '')
      }, (item: number) => item + '')
    }
  }
}

如何进行资源加载

资源分类简介:

|250

js
├── AppScope
│   ├── app.json5
│   └── resources
│       └── base // 默认存在
│           ├── element // 存放字符串、颜色、布尔值等基础元素
│           │   └── string.json
│           └── media // 表示媒体资源,包括图片、音视频等非文本格式的文件
│               └── app_icon.png
├── build-profile.json5
├── code-linter.json5
├── entry
│   ├── build
│   ├── ....
│   └── src
│       ├── main
│       │   ├── ...
│       │   ├── module.json5
│       │   └── resources
│       │       ├── base
│       │       │   ├── element // 存放字符串、颜色、布尔值等基础元素
│       │       │   │   ├── color.json
│       │       │   │   └── string.json
│       │       │   ├── media // 媒体文件
│       │       │   │   ├── background.png
│       │       │   │   ├── foreground.png
│       │       │   │   ├── layered_image.json
│       │       │   │   └── startIcon.png
│       │       │   └── profile // 自定义配置文件,必须json格式
│       │       │       ├── backup_config.json
│       │       │       └── main_pages.json
│       │       ├── en_US
│       │       │   └── element
│       │       │       └── string.json
│       │       ├── rawfile // 其他类型文件,原始文件形式保存
│       │       └── zh_CN
│       │           └── element
│       │               └── string.json
├── AppScope
│   ├── app.json5
│   └── resources
│       └── base // 默认存在
│           ├── element // 存放字符串、颜色、布尔值等基础元素
│           │   └── string.json
│           └── media // 表示媒体资源,包括图片、音视频等非文本格式的文件
│               └── app_icon.png
├── build-profile.json5
├── code-linter.json5
├── entry
│   ├── build
│   ├── ....
│   └── src
│       ├── main
│       │   ├── ...
│       │   ├── module.json5
│       │   └── resources
│       │       ├── base
│       │       │   ├── element // 存放字符串、颜色、布尔值等基础元素
│       │       │   │   ├── color.json
│       │       │   │   └── string.json
│       │       │   ├── media // 媒体文件
│       │       │   │   ├── background.png
│       │       │   │   ├── foreground.png
│       │       │   │   ├── layered_image.json
│       │       │   │   └── startIcon.png
│       │       │   └── profile // 自定义配置文件,必须json格式
│       │       │       ├── backup_config.json
│       │       │       └── main_pages.json
│       │       ├── en_US
│       │       │   └── element
│       │       │       └── string.json
│       │       ├── rawfile // 其他类型文件,原始文件形式保存
│       │       └── zh_CN
│       │           └── element
│       │               └── string.json

如何进行多module开发

开发时如何进行数据mock

考虑也是单独套一个@Preview修饰的组件,然后数据写死。用于快速开发预览。

调试包如何安装到真机

bash
hdc app install -r  xxxx.hap
hdc app install -r  xxxx.hap

如果生产包,需要走应用测试,类似苹果的testflight。