@Entry
页面的入口,像 Android的Activity一样
- API10 之后可以 携带LocalStorage,此组件的所有子组件实例将自动获得对该LocalStorage实例的访问权限
- API11 又可以 { routeName: "ddddd", storage: storage, useSharedStorage: true },useSharedStorage是api12加的
let storage:LocalStorage = new LocalStorage();
@Entry(storage)
@Component和@Preview
自定义组件,使用struct 结构体类似于class 名字驼峰,必须有build函数,如果在另外的文件中引用该自定义组件,需要使用export关键字导出,并在使用的页面import该自定义组件。使用@Preview可以预览,如果在 Entry 里面 就不用预览了
- @Component struct + 自定义组件名 + {...}的组合构成自定义组件
- 不能有继承关系。对于struct的实例化,可以省略new。
- build()函数:build()函数用于定义自定义组件的声明式UI描述,自定义组件必须定义build()函数 ,
- 组件中可以声明变量 和 函数 ,函数最好不要是static,属性也最好不是static
- 如果在另外的文件中引用该自定义组件,需要使用export关键字导出,并在使用的页面import该自定义组件。
- 调用的地方也可以链式调用其他通用属性
@Component({ freezeWhenInactive: true })api11提供了冻结,如果为true,那么像 link watch的如果在不活跃的时候,不会再走,当再次活跃就会重新走 ,有点像LiveData- build() 根节点必须是唯一的,
- 里面不允许使用变量,自己作用域,除了if else ,其他View,不能有除了组件外的代码
- build()只能调用 @builder 修饰的函数,相当于只能调用组件
- 不允许switch语法
- 不允许使用表达式
- 通用样式也可以写通用属性
@Builder
ArkUI还提供了一种更轻量的UI元素复用机制@Builder,@Builder所装饰的函数遵循build()函数语法规则,开发者可以将重复使用的UI元素抽象成一个方法,在build方法里调用。
- 组件内的Builder构建函数,该方法被认为是该组件的私有、特殊类型的成员函数。不能加funcation,直接可以访问Component的任何东西,不建议使用参数传递的方式
- 全局自定义构建函数 必须加funcation,全局的自定义构建函数可以被整个应用获取
Builder的传递值规则
- 参数的类型必须与参数声明的类型一致,不允许undefined、null和返回undefined、null的表达式。
@Builder function overBuilder(paramA1: string) {
Row() {
Text(`UseStateVarByValue: ${paramA1} `)
}
}
@Entry
@Component
struct Parent {
@State label: string = 'Hello';
build() {
Column() {
overBuilder(this.label)
}
}
}
- 在自定义构建函数内部,不允许改变参数值。如果需要改变参数值,且同步回调用点,建议使用@Link。
- 只有传入一个参数,且参数需要直接传入对象字面量才会按引用传递该参数,其余传递方式均为按值传递。使用 $$:表示引用传递
@Builder
function overBuilder($$: Dog) {
Row() {
Text(`UseStateVarByReference: ${$$.name} `)
}
}
class Dog {
name: string = '';
}
// 在Parent组件中调用overBuilder的时候,将this.label引用传递给overBuilder
overBuilder({ name: this.label })
Button('Click me').onClick(() => {
// 点击“Click me”后,UI从“Hello”刷新为“ArkUI”
this.label = 'ArkUI';
})
wrapBuilder 模板函数
- wrapBuilder方法只能传入全局@Builder方法。
- wrapBuilder方法返回的WrappedBuilder对象的builder属性方法只能在struct内部使用。
@BuilderParam
@Component 子组件中如果有 @BuilderParam参数,那么在创建这个Component的时候 ,必须传入BuilderParam参数,@BuilderParam用来装饰指向@Builder方法的变量
@Component
struct Child {
// 使用父组件@Builder装饰的方法初始化子组件@BuilderParam
@BuilderParam customBuilderParam: () => void;
build() {
Column() {
this.customBuilderParam()
}
}
}
@Entry
@Component
struct Parent {
@Builder componentBuilder() {
Text(`Parent builder `)
}
build() {
Column() {
// 创建子的时候,必须传递customBuilderParam的BuilderParam方法
Child({ customBuilderParam: this.componentBuilder })
}
}
}
闭包的方式,相当于lamalda表达式一样,不建议这样用
@Component
struct CustomContainer {
// 使用父组件的尾随闭包{}(@Builder装饰的方法)初始化子组件@BuilderParam
@BuilderParam closer: () => void
build() {
this.closer()
}
}
@Builder function specificParam(label1: string, label2: string) {
Column() {
Text(label1)
.fontSize(30)
Text(label2)
.fontSize(30)
}
}
@Entry
@Component
struct CustomContainerUser {
@State text: string = 'header';
build() {
Column() {
// 创建CustomContainer,在创建CustomContainer时,通过其后紧跟一个大括号“{}”形成尾随闭包
// 作为传递给子组件CustomContainer @BuilderParam closer: () => void的参数
CustomContainer() {
Column() {
specificParam('testA', 'testB')
}
}
}
}
}
@Styles
- 推荐小写,
不可以有参数,仅仅支持通用属性, - 组件内大于全局,组件内不需要function,全局需要,无论全局还是组件内
- 都不支持export 给别人使用
- 组件内的@Styles可以通过this访问组件的常量和状态变量,并可以在@Styles里通过事件来改变状态变量的值
@Styles function viewStyle(){
.width(200)
.height(200)
.borderWidth(2)
.borderColor(Color.Red)
}
stateStyles通用属性 多态样式
只提供下面的状态,里面可以使用@State一起变 也可以配合Styles使用,记住下面传递的是方法,不是方法调用,不加()
- focused:获焦态。
- normal:正常态。
- pressed:按压态。
- disabled:不可用态。
- selected 选中态 api10
@State focusedColor: Color = Color.Red;
@Styles pressedStyle() {
.backgroundColor(Color.Red)
}
Button('Button2')
.stateStyles({
focused: {
.backgroundColor(this.focusedColor)
},
pressed: this.pressedStyle(),
normal: {
.backgroundColor(Color.Red)
},
disabled: {
.backgroundColor(Color.Gray)
}
})
@Extends
扩展制定的样式
- 只能是全局的,不能是局部的,推荐小写,扩展原生组件样式
- 可以有 参数,也可以是function参数,event,比如点击事件
- 也会响应 @State color:Color 如果state 变了,Extends 也会变
- 只能在当前文件内使用,不支持export
@Extend(Text) function textExtend(){
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor(Color.Red)
.textAlign(TextAlign.Center)
}
AnimatableExtend api10 动画相关
在这个属性方法中修改组件不可动画的属性。在动画执行过程时,通过逐帧回调函数修改不可动画属性值,让不可动画属性也能实现动画效果。
-
可动画属性:如果一个属性方法在animation属性前调用,改变这个属性的值可以生效animation属性的动画效果,这个属性称为可动画属性。比如height、width、backgroundColor、translate属性,Text组件的fontSize属性等。
-
不可动画属性:如果一个属性方法在animation属性前调用,改变这个属性的值不能生效animation属性的动画效果,这个属性称为不可动画属性。比如Polyline组件的points属性等。
-
仅支持定义在全局,不支持在组件内部定义
-
定义的函数参数类型必须为number类型或者实现 AnimtableArithmetic接口的自定义类型
-
定义的函数体内只能调用@AnimatableExtend括号内组件的属性方法
@AnimatableExtend(Text) function animatableFontSize(size: number) {
.fontSize(size)
}
@State fontSize: number = 20
Text("AnimatableProperty")
.animatableFontSize(this.fontSize)
.animation({duration: 1000, curve: "ease"})
Button("Play")
.onClick(() => {
this.fontSize = this.fontSize == 20 ? 36 : 20
})
@Require装饰器:校验构造传参
- 创建的时候必须传递,否则编译不通过
- @Require装饰器仅用于装饰struct内的@Prop、@State、@Provide、@BuilderParam和普通变量(无状态装饰器修饰的变量)。
状态管理
相当于UI驱动数据,数据驱动UI,databinding的角色
- @State装饰的变量与子组件中的@Prop装饰变量之间建立单向数据同步
- 支持Object、class、string、number、boolean、enum类型,支持Date类型
- 比如是class ,如果只改变一个属性,他也会去通知使用这个属性的组件
- API11及以上支持Map、Set类型,还支持联合类型
- @State装饰的变量生命周期与其所属自定义组件的生命周期相同
- 必须要初始化
@State 组件内状态
@State 是状态装饰器,相当于MVVM ,Databinding的角色,数据驱动ui, ui驱动数据
- 允许 Object、class、string、number、boolean、enum类型,以及这些类型的数组。支持Date类型。
- class 的二级属性更改是监听不到的,比如User里面的Dog的name,是监听不到的
- 能监听到数组的删除添加以及赋值,但是如果数组中是对象,更改对象的内容,是观察不到的
- 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口set, clear, delete 更新Map的值
- API11及以上支持Map、Set类型。支持undefined和null类型。联合类型
- 被装饰的必须初始化
- 不能通过箭头函数间接的更改有关stage的数据,
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
@State user: User = new User('张三', new Dog('哈士奇'));
build() {
Column({ space: 10 }) {
Row(){
Image($r('app.media.demo')).width(100).height(100).border({width:1,color:Color.Red,radius:105})
}
Text(this.message)
.fontSize(50)
.onClick(() => {
this.message = "Hello huawei"
})
Text(`人的名字叫:${this.user.name}`)
.fontSize(30)
.onClick(() => {
// this.user.name = "李四"
this.user = new User('大哥',new Dog("111"))
})
Text(`狗的名字叫:${this.user.dog.name}`)
.fontSize(30)
.onClick(() => {
// this.user.dog.name = "金毛"
this.user.dog = new Dog("金毛")
console.log(`狗的名字叫:${this.user.dog.name}`)
})
}
.height('100%')
.width('100%')
}
}
@Prop 父子单向同步
可以认为深copy父亲中的数据
@Prop装饰的变量可以和父组件建立单向的同步关系。@Prop装饰的变量是可变的,但是变化不会同步回其父组件。
如果在自己组件中用法基本和@State一样
父的@State传递给子的@Prop ,单向传递
@Entry
@Component
struct ParentPage {
@State count: number = 1;
@State countLink: number = 1;
build() {
Column({ space: 20 }) {
Text(`我是父亲拿到的值:${this.count} -- ${this.countLink}`)
.fontSize(30)
.fontWeight(FontWeight.Bold)
Button('点我更改值')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.count += 1
})
Button('点我更改link值')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.countLink += 1
})
Child({ count: this.count, countLink: this.countLink })
}
.justifyContent(FlexAlign.Center)
.height('100%')
.width('100%')
}
}
@Component
struct Child {
@Link countLink: number
@Require @Prop count: number
build() {
Column() {
Text(`我是儿子拿到的值:${this.count} -- ${this.countLink}`)
.fontSize(30)
.fontWeight(FontWeight.Bold)
Button('点我更改值')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin({ top: 20 })
.onClick(() => {
this.count += 1
})
Button('点我更改link值')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.countLink += 1
})
}
}
}
@Link 子组件中被@Link装饰的变量与其父组件中对应的数据源建立双向数据绑定。
@Provide装饰器和@Consume装饰器
父与子孙后台双向通讯,
- @Provide和@Consume可以通过相同的变量名或者相同的变量别名绑定,建议类型相同,否则会发生类型隐式转换,从而导致应用行为异常。
// 通过相同的变量名绑定
@Provide a: number = 0;
@Consume a: number;
// 通过相同的变量别名绑定
@Provide('a') b: number = 0;
@Consume('a') c: number;
@Entry
@Component
struct Page {
@State message: string = 'Hello World';
// 通过相同的变量名绑定
@Provide count: number = 0;
build() {
Column() {
Text(`我是儿子拿到的值:${this.count}`)
.fontSize(30)
.fontWeight(FontWeight.Bold)
Button('点我更改值')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin({ top: 20 })
.onClick(() => {
this.count += 1
})
Child()
}
.height('100%')
.width('100%')
}
}
@Component
struct Child {
@Consume count: number;
build() {
Column() {
Text(`我是儿子拿到的值:${this.count}`)
.fontSize(30)
.fontWeight(FontWeight.Bold)
Button('点我更改值')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin({ top: 20 })
.onClick(() => {
this.count += 1
})
GrandSon()
}
}
}
@Component
struct GrandSon {
@Consume count: number;
build() {
Column() {
Text(`我是孙子拿到的值:${this.count}`)
.fontSize(30)
.fontWeight(FontWeight.Bold)
Button('点我更改值')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin({ top: 20 })
.onClick(() => {
this.count += 1
})
}
}
}
@Observed/@ObjectLink装饰器
上面的数组项中属性的赋值观察不到。class中class的属性更改观察不到,只能观察一级属性,所以 @ObjectLink和@Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步
- @ObjectLink装饰的变量不能被赋值,如果要使用赋值操作,请使用@Prop
- @ObjectLink装饰的变量和数据源的关系是双向同步,@ObjectLink装饰的变量相当于指向数据源的指针。禁止对@ObjectLink装饰的变量赋值,如果一旦发生@ObjectLink装饰的变量的赋值,则同步链将被打断。因为@ObjectLink装饰的变量通过数据源(Object)引用来初始化。对于实现双向数据同步的@ObjectLink,赋值相当于更新父组件中的数组项或者class的属性,TypeScript/JavaScript不能实现,会发生运行时报错。
- @ObjectLink只能接收被@Observed装饰class的实例,推荐设计单独的自定义组件来渲染每一个数组或对象
- ObjectLink 基本上也不能嵌套,放在自定义组件中
@Observed
export class User {
name: string = ''
dog: Dog = new Dog('')
constructor(name: string, dog: Dog) {
this.name = name;
this.dog = dog;
}
}
@Observed
export class Dog {
name: string = ''
constructor(name: string) {
this.name = name;
}
}
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
@State user: User = new User('张三', new Dog('哈士奇'));
build() {
Column({ space: 10 }) {
Row(){
Image($r('app.media.demo')).width(100).height(100).border({width:1,color:Color.Red,radius:105})
}
Text(this.message)
.fontSize(50)
.onClick(() => {
this.message = "Hello huawei"
})
Text(`人的名字叫:${this.user.name}`)
.fontSize(30)
.onClick(() => {
this.user.name = "李四"
})
Text(`狗111的名字叫:${this.user.dog.name}`)
.fontSize(30)
.onClick(() => {
this.user.dog.name = "阿拉斯加"
console.log(`狗的名字叫:${this.user.dog.name}`)
})
SonPage({dog:this.user.dog})
}
.height('100%')
.width('100%')
}
}
@Component
struct SonPage{
@ObjectLink dog: Dog
build() {
Text(`狗的名字叫:${this.dog.name}`)
.fontSize(30)
.onClick(() => {
this.dog.name = "金毛"
// this.user.dog = new Dog("金毛")
console.log(`狗的名字叫:${this.dog.name}`)
})
}
}
管理应用拥有的状态概述
- LocalStorage:页面级UI状态存储,通常用于UIAbility内、页面间的状态共享。
- AppStorage:特殊的单例LocalStorage对象,由UI框架在应用程序启动时创建,为应用程序UI状态属性提供中央存储。
- PersistentStorage:持久化存储UI状态,通常和AppStorage配合使用,选择AppStorage存储的数据写入磁盘,以确保这些属性在应用程序重新启动时的值与应用程序关闭时的值相同。
- Environment:应用程序运行的设备的环境参数,环境参数会同步到AppStorage中,可以和AppStorage搭配使用。
LocalStorage
LocalStorage是页面级的UI状态存储,通过@Entry装饰器接收的参数可以在页面内共享同一个LocalStorage实例。LocalStorage支持UIAbility实例内多个页面间状态共享。主要是配合@LocalStorageProp和@LocalStorageLink使用
- 组件树的根节点,即被@Entry装饰的@Component,可以被分配一个LocalStorage实例,此组件的所有子组件实例将自动获得对该LocalStorage实例的访问权限。
- @Component装饰的组件最多可以访问一个LocalStorage实例和AppStorage,未被@Entry装饰的组件不可被独立分配LocalStorage实例,只能接受父组件通过@Entry传递来的LocalStorage实例。一个LocalStorage实例在组件树上可以被分配给多个组件。
- LocalStorage中的所有属性都是可变的
- LocalStorage是页面级存储,getShared接口仅能获取当前Stage通过windowStage.loadContent传入的LocalStorage实例,否则返回undefined
- 将LocalStorage实例从UIAbility共享到一个或多个视图,那么下面的所有的Page都会拿得到
- 从API version 12开始,自定义组件支持接收LocalStorage实例,必须放到第二个参数Child({ }, localStorage4)
windowStage.loadContent('pages/Index', this.storage)
let storage = LocalStorage.getShared()
@Entry(storage)
LocalStorageLink
@LocalStorageLink(key)是和LocalStorage中key对应的属性建立双向数据同步:
- 本地修改发生,该修改会被写回LocalStorage中;
- LocalStorage中的修改发生后,该修改会被同步到所有绑定LocalStorage对应key的属性上,包括单向(@LocalStorageProp和通过prop创建的单向绑定变量)、双向(@LocalStorageLink和通过link创建的双向绑定变量)变量。
class UserStorage {
constructor(count: number) {
this.count = count;
}
count: number = 0;
}
let para: Record<string, object | number> = { 'keyName': 47, 'userStorage': new UserStorage(1) };
let storage: LocalStorage = new LocalStorage(para); // 创建新实例并使用给定对象初始化
@Entry(storage)
@Component
struct StorageDemo {
@State message: string = 'Hello World';
link: SubscribedAbstractProperty<number> = storage.link('keyName'); // link1.get() == 47
prop: SubscribedAbstractProperty<number> = storage.prop('keyName'); // prop.get() == 47
// @LocalStorageLink变量装饰器与LocalStorage中的'PropA'属性建立双向绑定
@LocalStorageLink('keyName') childLinkNumber: number = 1;
// @LocalStorageLink变量装饰器与LocalStorage中的'PropB'属性建立双向绑定
@LocalStorageLink('userStorage') childLinkObject: UserStorage = new UserStorage(0);
build() {
Column() {
Text(`link1 = ${this.childLinkNumber} prop = ${this.childLinkObject.count}`)
Button('设置link')
.fontSize(30)
.fontWeight(FontWeight.Bold)
.onClick(() => {
// 这里更改,那么storage里面的也会更改
this.childLinkNumber++
this.link.set(48); // two-way sync: link1.get() == link2.get() == prop.get() == 48
console.log(`link1 = ${this.link.get()} prop = ${this.prop.get()} get= ${storage.get<number>('keyName')}`)
})
Button('设置prop')
.fontSize(30)
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.prop.set(1); // one-way sync: prop.get() == 1; but link1.get() == link2.get() == 48
console.log(`link1 = ${this.link.get()} prop = ${this.prop.get()} get= ${storage.get<number>('keyName')}`)
})
Button('set storage')
.fontSize(30)
.fontWeight(FontWeight.Bold)
.onClick(() => {
storage.setOrCreate<number>('keyName', 1000)
console.log(`link1 = ${this.link.get()} prop = ${this.prop.get()} get= ${storage.get<number>('keyName')}`)
})
}
.height('100%')
.width('100%')
.justifyContent(FlexAlign.Center)
}
}
AppStorage:应用全局的UI状态存储
AppStorage是在应用启动的时候会被创建的单例。它的目的是为了提供应用状态数据的中心存储,这些状态数据在应用级别都是可访问的。AppStorage将在应用运行过程保留其属性。属性通过唯一的键字符串值访问。
持久化数据PersistentStorage和环境变量Environment都是通过AppStorage中转,才可以和UI交互。配合 @StorageProp和@StorageLink。
- 当@StorageProp(key) 单向同步
- @StorageLink(key)是和AppStorage中key对应的属性建立双向数据同步
不建议借助@StorageLink的双向同步机制实现事件通知
不建议开发者使用@StorageLink和AppStorage的双向同步的机制来实现事件通知,因为AppStorage中的变量可能绑定在多个不同页面的组件中,但事件通知则不一定需要通知到所有的这些组件。并且,当这些@StorageLink装饰的变量在UI中使用时,会触发UI刷新,带来不必要的性能影响。
PersistentStorage
同步的,不是异步的。本地存储,主要利用 AppStorage取值 来进行本地存储
- 不支持嵌套对象(对象数组,对象的属性是对象等)。
- PersistentStorage的持久化变量最好是小于2kb的数据,不要大量的数据持久化
- 最好是小于2kb的数据,不要大量的数据持久化
- PersistentStorage和UI实例相关联,持久化操作需要在UI实例初始化成功后(即loadContent传入的回调被调用时)才可以被调用,早于该时机调用会导致持久化失败。
// 存储完之后
PersistentStorage.persistProp('aProp', 47);
// 直接拿就可以了
@StorageLink('aProp') aProp: number = 48;
Environment:设备环境查询
@Watch 可以观察某个属性更改
配合link state prop
- 当观察到状态变量的变化(包括双向绑定的AppStorage和LocalStorage中对应的key发生的变化)的时候,对应的@Watch的回调方法将被触发;
- @Watch方法在自定义组件的属性变更之后同步执行;
- 如果在@Watch的方法里改变了其他的状态变量,也会引起状态变更和@Watch的执行;
- 在第一次初始化的时候,@Watch装饰的方法不会被调用,即认为初始化不是状态变量的改变。只有在后续状态改变时,才会调用@Watch回调方法。
@State @Watch('onCountUpdated') count: number = 0;
// @Watch 回调
onCountUpdated(): void {
console.log(`count 更改了 ${this.count}`)
}
@Track装饰器:class对象属性级更新,那是不是大量的需要这个属性???
也就是观察的Class ,如果 有属性增加@Track,那么只有这个属性更改的时候,这个View才会刷新,其他更改不变
class LogTrack {
@Track str1: string;
@Track str2: string;
constructor(str1: string) {
this.str1 = str1;
this.str2 = 'World';
}
}
class LogNotTrack {
str1: string;
str2: string;
constructor(str1: string) {
this.str1 = str1;
this.str2 = '世界';
}
}
@Entry
@Component
struct AddLog {
@State logTrack: LogTrack = new LogTrack('Hello');
@State logNotTrack: LogNotTrack = new LogNotTrack('你好');
isRender(index: number) {
console.log(`Text ${index} is rendered`);
return 50;
}
build() {
Row() {
Column() {
Text(this.logTrack.str1) // UINode1
.fontSize(this.isRender(1))
.fontWeight(FontWeight.Bold)
Text(this.logTrack.str2) // UINode2
.fontSize(this.isRender(2))
.fontWeight(FontWeight.Bold)
Button('change logTrack.str1')
.onClick(() => {
this.logTrack.str1 = 'Bye';
})
Text(this.logNotTrack.str1) // UINode3
.fontSize(this.isRender(3))
.fontWeight(FontWeight.Bold)
Text(this.logNotTrack.str2) // UINode4
.fontSize(this.isRender(4))
.fontWeight(FontWeight.Bold)
Button('change logNotTrack.str1')
.onClick(() => {
this.logNotTrack.str1 = '再见';
})
}
.width('100%')
}
.height('100%')
}
}
$$语法:内置组件双向同步
更databinding 一样属于双向绑定,也就是那几个控件
Swiper,Tabs,TextInput,Toggle,Refresh,GridItem,ListItem
Input,Checkbox,DatePicker,TimePicker,Panel,Radio,Slider