前提:基于官网3.1/4.0文档。参考官网文档 基于Android开发体系来进行比较和思考。(或有偏颇,自行斟酌)

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

基本语法

1.结构

上面是官网上面的图,从结构来看,类似Jetpack Compose。 通过注解的方式来定义组件,这里给命名为了“装饰器”。 需要注意的是:@State表示组件中的状态变量,状态变量变化会触发UI刷新。类比livedata

2.引用的方式

// $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作用域

fn = () => {

console.info(`counter: ${this.counter}`)

this.counter++

}

Button('add counter')

.onClick(this.fn)

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

4.组件

@Component

struct MyComponent {

build() {

}

}

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

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

build

//不能在build里声明变量,需要放在build外。

build() {

// 反例:不允许声明本地变量

let a: number = 1;

}

//不允许在UI描述里直接使用console.info,但允许在方法或者函数里使用——>可以理解为build{}是渲染作用域,不要添加打印

build() {

// 反例:不允许console.info

console.info('print debug log');

}

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

build() {

// 反例:不允许本地作用域

{

...

}

}

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

build() {

Column() {

// 反例:不允许使用switch语法

switch (expression) {

case 1:

Text('...')

break;

case 2:

Image('...')

break;

default:

Text('...')

break;

}

}

}

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,然后执行初始化新页面的生命周期流程。

以下是官方示例:

// 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方法里面调用是指什么?有没有对比的案例?

待解答

//定义私有组件

@Builder MyBuilderFunction(){ ... }

//调用私有组件

this.MyBuilderFunction()

//定义全局组件

@Builder function MyGlobalBuilderFunction(){ ... }

//调用全局组件

MyGlobalBuilderFunction()

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

值传递和引用传递

引用传递

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

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刷新。

值传递

@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装饰的方法)初始化。

案例

@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。

怎么用?

// 定义在全局的@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。当然,并没有解释为什么不可以,估计与设计逻辑冲突有关

怎么用?

@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%')

}

}

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

@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

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

怎么用

@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:不可用态。

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

参考链接

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。