在这两天的学习过程中,我发现了一个巨诡异的事情,那就是明明第一次在文档中找到的内容,结果第二次想要通过搜索找到他,打死都找不到,这种情况出现了三次。arkUI 的文档是真神奇!
那种诡异感甚至差点让我以为自己穿越了...
第二天没有什么实际的产出,但是学习进展非常明显,直接把自定义组件相关的知识 ~
学!完!了!
我把主要学习精力放在了区别 arkUI 和 React/vue 的差异、以及在封装一些比较有难度的基础组件的实现方式上。
比如我想要封装一个体验良好的表单组件,那么我的学习思路是
- T、学习思路
- 1、复杂UI布局应该怎么做 ✅
- 2、基础的动画细节应该如何实现 ✅
- 3、数据管理应该怎么做 ✅
- 4、表单验证应该怎么做 ✅
- 5、数据量复杂的时候有没有可能存在性能问题,还没有验证过,若在实践中遇到问题,针对性的优化即可
如果目的一样,当我攻克了和 React 的差异,在 HarmonyOS 上基于 arkUI 实现一套大厂可商用的基础组件就比较简单了。
这意味着什么呢?
意味着我已经可以开始撸大型实战项目啦!剩下还有很多不熟悉的组件和系统能力,只需要在用到的时候,阅读文档拿来使用,在后续的开发过程中慢慢熟练即可。所以呢,有想要组建 HarmonyOS 开发团队的老板们可以开始联系我了,我给你们当技术指导,哈哈。
我今天学习的主要内容包括:
系统组件生命周期 |
onAppear:组件挂载到组件树之后调用 |
onDisAppear:组件卸载时调用 |
页面组件生命周期 |
onPageShow:页面每次显示时触发 |
onPageHide:页面每次隐藏时触发 |
onBackPress:用户点击返回按钮时触发 |
自定义组件生命周期 |
aboutToAppear:组件即将出现时触发,在 build 之前 |
aboutToDisappear:组件即将销毁时触发 |
组件级别状态管理 |
@State |
@Prop |
@Link |
@Provide/@Consume |
@Observed |
@ObjectLink |
应用级别状态管理 |
@StorageProp |
@StorageLink |
@LocalStorageProp |
@LocalStorageLink |
其他状态管理 |
@Watch |
$$ 运算符 |
T. 状态交互 |
1. 传参与接受参数 |
2. 子组件控制父组件 |
3. 父组件控制子组件 |
练习了大量的交互 Demo,并成功封装了两个可商用的基础组件
Icon | Lottie |
字体图标组件 | Lottie 动画组件 |
在 React 里,一个 state 一个 props 就完事了,万万没想到在 arkUI 里知识点这么多。在研究组件交互的过程中,我的 CPU 直接被干冒烟了,根据我的学习感受,这必定会成为一个比较难掌握的点,要吃透他不太容易
01组件分类
在 arkUI 里,组件主要分为三个大类
- 系统组件 主要是指系统原生提供的一系列组件,例如 Text、Button、CheckBox 等
- 页面组件 被装饰器 @Entry 装饰的组件为页面组件,他表示一个页面的入口,该组件为页面的根节点
- 自定义组件 我们可以通过装饰器 @Component 定义新的组件
02组件基础语法
一个页面组件中最基础的写法如下
@Entry @Component struct MyComponent { build() { Text('hello world!') } }
页面组件必须被 @Entry 装饰。build 方法中包含所有的页面元素。在上面这个例子中,Text 组件执行实际上是一个初始化的过程,但是由于关键字 struct 的作用,因此省略了 new.
系统提供的基础组件以 . 链式调用的方式设置当前组件的样式
例如,设置 Text 组件的字体大小
Text('test') .fontSize(12)
也可以同时配置多个属性
Image('test.jpg') .alt('error.jpg') .width(100) .height(100)
除了直接传递常量参数之外,还可以传递变量和表达式
Text('hello') .fontSize(this.size) Image('test.jpg') .width(this.count % 2 === 0 ? 100 : 200) .height(this.offset + 100)
arkUI 系统提供了一些全局的枚举类型,可以作为参数传递
Text('hello') .fontSize(20) .fontColor(Color.Red) .fontWeight(FontWeight.Bold)
如果某个元素想要设置事件监听,同样以 . 链式调用的方式使用
Button('Click me') .onClick(() => { this.myText = 'ArkUI'; })
this
习惯了箭头函数和函数式组件的 React 开发者就要注意了,在面向对象的语境下,需要随时确保 this 的引用发生变化,因此如下两种情况都需要使用 bind 绑定 this
Button('add counter') .onClick(function(){ this.counter += 2; }.bind(this))
这是个坑啊,折磨了多少开发者,他又来了!
myClickHandler(): void { this.counter += 2; } ... Button('add counter') .onClick(this.myClickHandler.bind(this))
组件嵌套
组件嵌套的方式,就是在父组件后面添加 {},所有的容器组件都支持嵌套子组件
Column() { Text('Hello') .fontSize(100) Divider() Text(this.myText) .fontSize(100) .fontColor(Color.Red) }
@Builder
有的时候我们希望把一段组件的逻辑单独抽离出来,可以使用 @Builder 装饰器来声明一个自定义构建函数
@Entry @Component struct MyComponent { @Builder helloHarmonyOS() { Text('Hello HarmonyOS') } build() { Row() { Text('hello world!') this.helloHarmonyOS() } } }
但是这个 @Builder 装饰器在传参数的时候,有特别的规则,我们后面在学习状态管理的时候一起分享
我们可以在一个组件里定义多个 @Builder 声明的函数,也可以在全局定义
@Builder function MyGlobalBuilderFunction() { }
@Styles
我们可以使用 @Styles 装饰器来解决样式复用的问题。
@Entry @Component struct MyComponent { @Builder helloHarmonyOS() { Text('Hello HarmonyOS') .fancy() } @Styles fancy() { .width('100%') .height(40) .backgroundColor(Color.Pink) } build() { Row() { Text('hello world!') .fancy() this.helloHarmonyOS() } } }
需要注意的是,@Styles 目前仅支持通用属性和通用事件。
具体属性有哪些可以去官方文档查看,或者根据 DevEco 的代码提示来编写
@Styles 也不支持传入参数
// bad @Styles - function globalFancy (value: number) { - .width(value) }
@Styles 可以定义在组件内,也可以定义在全局,在全局定义时需要在方法名之前添加 function 关键字
// 全局 @Styles function functionName() { ... } // 在组件内 @Component struct FancyUse { @Styles fancy() { .height(100) } }
组件内 @Styles 的优先级高于全局 @Styles。 框架优先找当前组件内的 @Styles,如果找不到,则会全局查找。
// 定义在全局的@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 还不支持跨文件引入,这里的全局只能是同一个文件的全局 ~
@Extend
我们可以使用 @Extend 扩展原生组件样式
@Extend(UIComponentName) function functionName { ... }
- @Extend 仅支持全局定义
- @Extend 支持封装指定原生组件的私有属性和方法,以及相同指定组件的 @Extend 方法
// 支持Text的私有属性fontColor @Extend(Text) function fancy () { .fontColor(Color.Red) } // superFancyText可以调用预定义的fancy @Extend(Text) function superFancyText(size:number) { .fontSize(size) .fancy() }
- @Extend 支持参数传入
// xxx.ets @Extend(Text) function fancy (fontSize: number) { .fontColor(Color.Red) .fontSize(fontSize) } @Entry @Component struct FancyUse { build() { Row({ space: 10 }) { Text('Fancy') .fancy(16) Text('Fancy') .fancy(24) } } }
传入的参数可以是 function
@Extend(Text) function makeMeClick(onClick: () => void) { .backgroundColor(Color.Blue) .onClick(onClick) } @Entry @Component struct FancyUse { @State label: string = 'Hello World'; onClickHandler() { this.label = 'Hello ArkUI'; } build() { Row({ space: 10 }) { Text(`${this.label}`) .makeMeClick(this.onClickHandler.bind(this)) } } }