鸿蒙 ArkTs初识

前提:基于官网3.1/4.0文档。参考官网文档

基于Android开发体系来进行比较和思考。(或有偏颇,自行斟酌)

吐槽:官网上的案例只有代码和文档解释,没有可以直接运行查看效果的模拟器,这一点上,Jetpack Compose是有的。

基本语法

1.结构

上面是官网上面的图,从结构来看,类似Jetpack Compose。 通过注解的方式来定义组件,这里给命名为了"装饰器"。

需要注意的是:@State表示组件中的状态变量,状态变量变化会触发UI刷新。类比livedata

2.引用的方式

ts 复制代码
// $r形式引入应用资源,可应用于多语言场景
Text($r('app.string.title_value'))
Image(this.imagePath)
Image('https://' + this.imageUrl)
Text(`count: ${this.count}`)// 这种方式在react中较为常见,应该是ts的写法

引用本地资源的方式是$r('app.string.title_value')。(感觉稍显复杂了)

3.this作用域

ts 复制代码
fn = () => {
  console.info(`counter: ${this.counter}`)
  this.counter++
}
Button('add counter')
  .onClick(this.fn)

可以看到,this作用域是整个文件,而不是Android中的闭包作用域。

4.组件

ts 复制代码
@Component
struct MyComponent {
 build() {
 }
}

自定义组件标准范式如上。加上注解@Entry装饰的自定义组件将作为UI页面的入口。(这句话比较难理解,可以认为是export之类的用法,一个文件只有一个export,React项目中的特点。)

@Entry可以接受一个可选的LocalStorage的参数。------>这点类似于Android中的bundle,用来进行UI页面的数据传递。不确定该LocalStorage是否是livedata特点。

build
ts 复制代码
//不能在build里声明变量,需要放在build外。
build() {
  // 反例:不允许声明本地变量
  let a: number = 1;
}

//不允许在UI描述里直接使用console.info,但允许在方法或者函数里使用------>可以理解为build{}是渲染作用域,不要添加打印
build() {
  // 反例:不允许console.info
  console.info('print debug log');
}

//不允许创建本地的作用域------>既然是渲染作用域,那就不必要再添加作用域,直接堆砌组件即可。(组件作用域里面可以调用方法)
build() {
  // 反例:不允许本地作用域
  {
    ...
  }
}

不允许switch语法,如果需要使用条件判断,请使用if------>这一点甚是奇怪,都是判断语句还做不一样的差异对待

ts 复制代码
build() {
  Column() {
    // 反例:不允许使用switch语法
    switch (expression) {
      case 1:
        Text('...')
        break;
      case 2:
        Image('...')
        break;
      default:
        Text('...')
        break;
    }
  }
}
ts 复制代码
build() {
  Column() {
    // 反例:不允许使用表达式------>实际上这种写法在React中很常见,不确定知道jetpack compose是否如此设计
    (this.aVar > 10) ? Text('...') : Image('...')
  }
}
生命周期

页面生命周期,即被@Entry装饰的组件生命周期,提供以下生命周期接口:

onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景,仅@Entry装饰的自定义组件生效。

onPageHide:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景,仅@Entry装饰的自定义组件生效。

onBackPress:当用户点击返回按钮时触发,仅@Entry装饰的自定义组件生效。
组件生命周期,即一般用@Component装饰的自定义组件的生命周期,提供以下生命周期接口:

aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行。

aboutToDisappear:在自定义组件析构销毁之前执行。不允许在aboutToDisappear函数中改变状态变量,特别是@Link变量的修改可能会导致应用程序行为不稳定。

生命周期相较于Android是做了减法。不确定是否有页面栈的设计模式,譬如说:栈顶复用之类,以及由此进行的bundle数据传递。

组件的销毁

组件的销毁是从组件树上直接摘下子树,所以先调用父组件的aboutToDisappear,再调用子组件的aboutToDisappear,然后执行初始化新页面的生命周期流程。

以下是官方示例:

ts 复制代码
// Index.ets
import router from '@ohos.router';

@Entry
@Component
struct MyComponent {
  @State showChild: boolean = true;

  // 只有被@Entry装饰的组件才可以调用页面的生命周期
  onPageShow() {
    console.info('Index onPageShow');
  }
  // 只有被@Entry装饰的组件才可以调用页面的生命周期
  onPageHide() {
    console.info('Index onPageHide');
  }

  // 只有被@Entry装饰的组件才可以调用页面的生命周期
  onBackPress() {
    console.info('Index onBackPress');
  }

  // 组件生命周期
  aboutToAppear() {
    console.info('MyComponent aboutToAppear');
  }

  // 组件生命周期
  aboutToDisappear() {
    console.info('MyComponent aboutToDisappear');
  }

  build() {
    Column() {
      // this.showChild为true,创建Child子组件,执行Child aboutToAppear
      if (this.showChild) {
        Child()
      }
      // this.showChild为false,删除Child子组件,执行Child aboutToDisappear
      Button('delete Child').onClick(() => {
        this.showChild = false;
      })
      // push到Page2页面,执行onPageHide
      Button('push to next page')
        .onClick(() => {
          router.pushUrl({ url: 'pages/Page2' });
        })
    }

  }
}

@Component
struct Child {
  @State title: string = 'Hello World';
  // 组件生命周期
  aboutToDisappear() {
    console.info('[lifeCycle] Child aboutToDisappear')
  }
  // 组件生命周期
  aboutToAppear() {
    console.info('[lifeCycle] Child aboutToAppear')
  }

  build() {
    Text(this.title).fontSize(50).onClick(() => {
      this.title = 'Hello ArkUI';
    })
  }
}
@Builder作用

用来自定义构建函数,它就是个标识符。

ArkUI还提供了一种更轻量的UI元素复用机制@Builder,@Builder所装饰的函数遵循build()函数语法规则,开发者可以将重复使用的UI元素抽象成一个方法,在build方法里调用。

==所以这到底是个什么作用?==有没有这个的区别是什么,在build方法里面调用是指什么?有没有对比的案例?

待解答

ts 复制代码
//定义私有组件
@Builder MyBuilderFunction(){ ... }
//调用私有组件
this.MyBuilderFunction()

//定义全局组件
@Builder function MyGlobalBuilderFunction(){ ... }
//调用全局组件
MyGlobalBuilderFunction()

可以看到,这里的function的作用,是将组件定义为全局的修饰符,而并不是函数的声明修饰符。这点容易让人误解

值传递和引用传递

引用传递

ArkUI提供$$作为按引用传递参数的范式 吐槽这个双美元符号作为范式,真的比较繁琐

ts 复制代码
ABuilder( $$ : { paramA1: string, paramB1 : string } );

@Builder function ABuilder($$: { paramA1: string }) {
  Row() {
    Text(`UseStateVarByReference: ${$$.paramA1} `)
  }
}
@Entry
@Component
struct Parent {
  @State label: string = 'Hello';
  build() {
    Column() {
      // 在Parent组件中调用ABuilder的时候,将this.label引用传递给ABuilder
      ABuilder({ paramA1: this.label })
      Button('Click me').onClick(() => {
        // 点击"Click me"后,UI从"Hello"刷新为"ArkUI"
        this.label = 'ArkUI';
      })
    }
  }
}

和TS使用方式是类似的,只不过这里的引用方式传值是带有状态的,即指向的引用值发生变化,也会引发@Builder方法内的UI刷新。

值传递

ts 复制代码
@Builder function ABuilder(paramA1: string) {//注意这个基本数据类型,是小写的string,与TS一致
  Row() {
    Text(`UseStateVarByValue: ${paramA1} `)
  }
}
@Entry
@Component
struct Parent {
  @State label: string = 'Hello';
  build() {
    Column() {
      ABuilder(this.label)
    }
  }
}

可以看到:很明显不是使用范式来进行参数的传递,而是场景的传参方式。这种值传递的方式和引用方式的传递的区别在于:前者值的变更不会触发@Builder组件的UI刷新。

@BuilderParam

作用是什么?

当开发者创建了自定义组件,并想对该组件添加特定功能时,例如在自定义组件中添加一个点击跳转操作。若直接在组件内嵌入事件方法,将会导致所有引入该自定义组件的地方均增加了该功能。为解决此问题,ArkUI引入了@BuilderParam装饰器,@BuilderParam用来装饰指向@Builder方法的变量,开发者可在初始化自定义组件时对此属性进行赋值,为自定义组件增加特定的功能。该装饰器用于声明任意UI描述的一个元素,类似slot占位符。

由此看来,其实类似于Kotlin中的扩展函数。(注意扩展函数是扩展"函数",不能扩展成员变量。暂不能确认这里是否可以扩展成员变量)

如何用?

@BuilderParam装饰的方法只能被自定义构建函数(@Builder装饰的方法)初始化。

案例

ts 复制代码
@Component
struct Child {
  @BuilderParam aBuilder0: () => void;

  build() {
    Column() {
      this.aBuilder0()
    }
  }
}

@Entry
@Component
struct Parent {
  @Builder componentBuilder() {
    Text(`Parent builder `)
  }

  build() {
    Column() {
      Child({ aBuilder0: this.componentBuilder })
    }
  }
}

官方举的案例硬是没看懂它的实际用途。------>实际上它类似于Android中addView(View)方法中的参数View,你可以丢进去LinearLayout、Button等等,它就是个抽象的占位参数。

@Styles

是什么*

样式装饰器

做什么用?

样式的集合,可以用来重用。类似于Android styles文件中定义的style。

怎么用?

ts 复制代码
// 定义在全局的@Styles封装的样式
@Styles function globalFancy  () {
  .width(150)
  .height(100)
  .backgroundColor(Color.Pink)
}

@Entry
@Component
struct FancyUse {
  @State heightValue: number = 100
  // 定义在组件内的@Styles封装的样式
  @Styles fancy() {
    .width(200)
    .height(this.heightValue)
    .backgroundColor(Color.Yellow)
    .onClick(() => {
      this.heightValue = 200
    })
  }
  build() {
    Column({ space: 10 }) {
      // 使用全局的@Styles封装的样式
      Text('FancyA')
        .globalFancy ()
        .fontSize(30)
      // 使用组件内的@Styles封装的样式
      Text('FancyB')
        .fancy()
        .fontSize(30)
    }
  }
}
  • @Styles仍旧是类似于@Builder一样,分为私有和全局定义(加上function修饰,就是全局)
  • @Styles里面包含了对点击事件的封装(也有可能有其他事件,譬如触摸事件),重点是它包含了"事件",而不单单是通常意义上的样式
  • 引用方式直接就是通过链式的.操作
  • 不确定后续的style(包含组件本身的属性)是否会覆盖@Style的属性
@Extend

是什么?

@Styles用于样式的扩展,在@Styles的基础上,我们提供了@Extend,用于扩展原生组件样式------>也就是@Styles为自定义组件扩展样式,而这个@Extend是为原生组件扩展样式(譬如Text就是原生组件)
为什么要区分两种标识符?

什么作用?

作用是和@Styles一样的,不过它可以传参,@Styles的函数时不可以传参的。这意味着它更灵活。但是它只能在组件内部定义,而不能全局定义,也就是不能使用function。当然,并没有解释为什么不可以,估计与设计逻辑冲突有关

怎么用?

ts 复制代码
@Entry
@Component
struct FancyUse {
  @State label: string = 'Hello World'

  build() {
    Row({ space: 10 }) {
      Text(`${this.label}`)
        .fontStyle(FontStyle.Italic)
        .fontWeight(100)
        .backgroundColor(Color.Blue)
      Text(`${this.label}`)
        .fontStyle(FontStyle.Italic)
        .fontWeight(200)
        .backgroundColor(Color.Pink)
      Text(`${this.label}`)
        .fontStyle(FontStyle.Italic)
        .fontWeight(300)
        .backgroundColor(Color.Orange)
    }.margin('20%')
  }
}

上面是一种通用写法,一种样式写了三次,那么优化之后如下:

ts 复制代码
@Extend(Text) function fancyText(weightValue: number, color: Color) {
  .fontStyle(FontStyle.Italic)
  .fontWeight(weightValue)
  .backgroundColor(color)
}

@Entry
@Component
struct FancyUse {
  @State label: string = 'Hello World'

  build() {
    Row({ space: 10 }) {
      Text(`${this.label}`)
        .fancyText(100, Color.Blue)
      Text(`${this.label}`)
        .fancyText(200, Color.Pink)
      Text(`${this.label}`)
        .fancyText(300, Color.Orange)
    }.margin('20%')
  }
}

很明显,封装性更好,可读性更强了。

stateStyles

是什么

状态样式,这样不好理解。关于组件状态的回调------>这里理解可能比较清晰。(具体见下面的案例)

怎么用

ts 复制代码
@Entry
@Component
struct CompWithInlineStateStyles {
  @State focusedColor: Color = Color.Red;
  normalColor: Color = Color.Green

  build() {
    Column() {
      Button('clickMe').height(100).width(100)
        .stateStyles({
          normal: {
            .backgroundColor(this.normalColor)
          },
          focused: {
            .backgroundColor(this.focusedColor)
          }
        })
        .onClick(() => {
          this.focusedColor = Color.Pink
        })
        .margin('30%')
    }
  }
}

focused:获焦态。

normal:正常态。

pressed:按压态。

disabled:不可用态。

因此,它至少上面四种状态可供回调设置样式。------>不确定是否可以在这个状态回调方法进行其他逻辑处理。(可能不可以?因为是使用的.方法的方式

相关推荐
HMS Core2 分钟前
【FAQ】HarmonyOS SDK 闭源开放能力 —Vision Kit (3)
华为·harmonyos
bestadc27 分钟前
鸿蒙 Location Kit(位置服务)
harmonyos
爱笑的眼睛1128 分钟前
HarmonyOS Navigation组件深度解析与应用实践
harmonyos·harmonyos next
鸿蒙布道师28 分钟前
鸿蒙NEXT开发动画案例9
android·ios·华为·harmonyos·鸿蒙系统·arkui·huawei
bestadc29 分钟前
鸿蒙 Background Tasks Kit(后台任务开发服务)
harmonyos
Bruce_Liuxiaowei8 小时前
HarmonyOS NEXT~鸿蒙应用上架指南:HarmonyOS应用发布全流程解析
华为·harmonyos
lqj_本人9 小时前
鸿蒙OS&UniApp开发的商品详情展示页面(鸿蒙系统适配版)#三方框架 #Uniapp
华为·uni-app·harmonyos
魔术师ID10 小时前
HarmonyOS开发样式布局
华为·harmonyos
yenggd11 小时前
防火墙旁路部署经典使用案例
网络·华为
HarmonyOS_SDK12 小时前
探索自定义地图样式,打造应用专属个性化地图
harmonyos