一、@Provide @Consume
父组件与子组件的子组件(官方叫法:后代组件 )双向同步数据(即,父组件与后代组件可以相互操作 @Provide
修饰的数据)
注意:@Provide
与 @Consume
声明的变量名必须一致。
typescript
import {TestChild } from './TestChild'
@Entry //这是一个页面
@Component // 页面中有一个视图容器,即根布局 Row()
struct Index {
@Provide msg: string = '混沌'
count: number = 0
build(){
Row(){
Column( {space : 20} ) {
Text(this.msg)
.fontSize(30)
.fontWeight(FontWeight.Bold)
Button('Parent 更新文字内容')
.onClick( ()=>{
this.msg = 'Hello World ' + (this.count++)
})
TestChild()
}.width('100%')
}.height('100%')
}
}
TestChild 嵌套 TestChild2, TestChild2嵌套TestChild3
typescript
@Component
export struct TestChild{
build(){
TestChild2(){
.width('100%')
.backgroundColor(Color.Red)
.align(Alignment.Center)
}
}
}
@Component
export struct TestChild2{
build(){
TestChild3()
}
}
@Component
export struct TestChild3{
@Consume msg: string
count: number = 0
build(){
Column(){
Text(this.msg).fontSize(30)
Button('TestChild2 更新文字内容')
.onClick( ()=>{
this.msg = 'HarmonyOS - Child' + (this.count++)
})
}.backgroundColor(Color.Pink)
}
}
二、@Observed @ObjectLink
父组件与嵌套对象或数组进行双向向同步数据。
说明:实际业务研发中,封装好多类(与 @Component
修饰的组件无关),这个时候,如果要让父组件和 嵌套对象进行数据同步,前边所介绍的所有装饰器是无法做到的。
子组件中
@ObjectLink
装饰器装饰的状态变量用于接收@Observed
装饰的类实例,和父组件中对应的状态变量建立双向数据绑定。单独使用
@Observed
是没有任何作用的,需要搭配@ObjectLink
或者@Prop
初始状态。
typescript
// 引起此问题初始化代码
@State b: ClassB = new ClassB(new ClassA(0));
// 修改
@State a: ClassA = new ClassA(0)
@State b: ClassB = new ClassB(a)
import {ClassA, ClassB, TestChild } from './TestChild'
@Entry //这是一个页面
@Component //页面中有一个视图容器,即根布局 Row()
struct Index {
@State b: ClassB = new ClassB(new ClassA(0));
build() {
Row() {
Column( {space : 20} ) {
Text(this.b.a.c + '')
.fontSize(30)
.fontWeight(FontWeight.Bold)
Button('Parent 更新文字内容')
.onClick( ()=>{
this.b.a.c += 1;
})
TestChild({a: this.b.a})
}.width('100%')
}.height('100%')
}
}
@Component
export struct TestChild {
@ObjectLink a: ClassA;
build(){
Column(){
Text(this.a.c + '').fontSize(30)
Button('TestChild2 更新文字内容')
.onClick( ()=>{
this.a.c += 1;
} )
}.backgroundColor(Color.Pink)
}
}
@Observed
export class ClassA {
public c: number;
constructor(c: number) {
this.c = c;
}
}
export class ClassB {
public a: ClassA;
constructor(a: ClassA) {
this.a = a;
}
}
三、@Watch
关注某个变量状态发生变化。
注意📢:监听的这个变量不要放在回调方法中,让其发生二次变化,容易导致死循环。
typescript
import {ClassA, ClassB, TestChild } from './TestChild'
@Entry //这是一个页面
@Component //页面中有一个视图容器,即根布局 Row()
struct Index {
@State msg: string = '混沌'
@State index: number = 0;
build(){
Row(){
Column( {space : 20} ) {
Text(this.msg + ' ' + this.index)
.fontSize(30)
.fontWeight(FontWeight.Bold)
Button('Parent 更新文字内容')
.onClick( ()=>{
this.index++
})
TestChild({count: this.index})
}.width('100%')
}.height('100%')
}
}
NOTE:使用 @Prop
修饰的原因:感知父组件改变 count 值。
typescript
@Component
export struct TestChild{
@Prop @Watch('onCountUpdated') count: number;
@State total: number = 0;
// @Watch 回调
onCountUpdated(propName: string): void {
this.total += 1;
}
build(){
Column(){
Text('HarmonyOS - Child' + this.total).fontSize(30)
Button('TestChild2 更新文字内容')
.onClick( ()=>{
this.count++
})
}.backgroundColor(Color.Pink)
}
}
四、@LocalStorageLink @LocalStorageProp
LocalStorage
是页面级的UI状态存储,通过@Entry
装饰器接收的参数可以在页面内共享同一个LocalStorage
实例。LocalStorage
也可以在UIAbility
内,页面间共享状态。
LocalStorage
在场景使用过程中包含了两个装饰器,即@LocalStorageLink
和 @LocalStorageProp
。
typescript
import { TestChild } from './TestChild';
// 创建新实例并使用给定对象初始化
let storage = new LocalStorage({ 'PropA': 47 });
// 使LocalStorage可从@Component组件访问
@Entry(storage)
@Component
struct Index {
// @LocalStorageLink变量装饰器与LocalStorage中的'PropA'属性建立双向绑定
@LocalStorageLink('PropA') count: number = 1;
build(){
Row(){
Column( {space : 20} ){
Text('混沌 ' + this.count)
.fontSize(30)
.fontWeight(FontWeight.Bold)
Button('Parent 更新文字内容')
.onClick( ()=>{
this.count++
})
TestChild()
}.width('100%')
}.height('100%')
}
}
@Component
export struct TestChild {
// @LocalStorageLink变量装饰器与LocalStorage中的'PropA'属性建立双向绑定
@LocalStorageLink('PropA') count: number = 1;
build() {
Column( {space : 20} ) {
Text('HarmonyOS - Child' + this.count)
.fontSize(30)
.fontWeight(FontWeight.Bold)
Button('TestChild2 更新文字内容')
.onClick( ()=>{
this.count++
})
}.width('100%')
.backgroundColor(Color.Pink)
}
}
总结,本例展示了:
- 使用构造函数创建
LocalStorage
实例storage; - 使用
@Entry
装饰器将storage添加到 Index 顶层组件中; @LocalStorageLink
绑定LocalStorage
对给定的属性,建立双向数据同步;
typescript
import { TestChild } from './TestChild';
// 创建新实例并使用给定对象初始化
let storage = new LocalStorage({ 'PropA': 47 });
// 使LocalStorage可从@Component组件访问
@Entry(storage)
@Component
struct Index {
// @LocalStorageLink变量装饰器与LocalStorage中的'PropA'属性建立双向绑定
@LocalStorageProp('PropA') count: number = 1;
build() {
Row() {
Column( {space : 20} ) {
Text('混沌 ' + this.count)
.fontSize(30)
.fontWeight(FontWeight.Bold)
Button('Parent 更新文字内容')
.onClick( ()=>{
this.count++
})
TestChild()
}.width('100%')
}.height('100%')
}
}
let storage = LocalStorage.GetShared()
@Component
export struct TestChild{
// @LocalStorageLink变量装饰器与LocalStorage中的'PropA'属性建立双向绑定
@LocalStorageLink('PropA') count: number = 1;
build() {
Column( {space : 20} ) {
Text('HarmonyOS - Child' + this.count)
.fontSize(30)
.fontWeight(FontWeight.Bold)
Button('TestChild2 更新文字内容')
.onClick( ()=>{
this.count++
})
}.width('100%')
.backgroundColor(Color.Pink)
}
}
@LocalStorageLink(key)
是和LocalStorage中key对应的属性建立双向数据同步:
本地修改发生,该修改会被写回LocalStorage中;
LocalStorage中的修改发生后,该修改会被同步到所有绑定LocalStorage对应key的属性上,包括单向(@LocalStorageProp
和通过prop创建的单向绑定变量)、双向(@LocalStorageLink
和通过link创建的双向绑定变量)变量。
这个例子中TestChild组件使用了@LocalStorageLInk, 当其值发生变化时,会同时影响到父布局使用到 @LocalStorageProp 装饰器的变量值,即 子组件的变量通过LocalStorage可以影响到相应的父组件变量值,但父组件的相关变量值是无法影响到子组件的变量值
五、@StorageLink @StorageProp
AppStorage
是应用全局的UI状态存储,是和应用的进程绑定的,由UI框架在应用程序启动时创建,为应用程序UI状态属性提供中央存储。
AppStorage
在场景使用过程中包含了两个装饰器,即@StorageLink
和 @StorageProp
和AppStorage不同的是,LocalStorage是页面级的,通常应用于页面内的数据共享。而AppStorage是应用级的全局状态共享,还相当于整个应用的"中枢",持久化数据PersistentStorage和环境变量Environment都是通过和AppStorage中转,才可以和UI交互。
注⚠️:AppStorage 和 LocalStorage是互不影响的!
typescript
import { TestChild } from './TestChild';
AppStorage.SetOrCreate('PropA', 47);
// 创建新实例并使用给定对象初始化
let storage = new LocalStorage();
// 使LocalStorage可从@Component组件访问
@Entry(storage)
@Component
struct Index {
// @LocalStorageLink变量装饰器与LocalStorage中的'PropA'属性建立双向绑定
@StorageLink('PropA') count: number = 1;
@LocalStorageLink('PropA') countL: number = 1;
build() {
Row(){
Column( {space : 20} ) {
Text('AppStorage ' + this.count)
.fontSize(30)
.fontWeight(FontWeight.Bold)
Button('更新AppStorage内容')
.onClick( ()=>{
this.count++
})
Text('LocalStorage ' + this.countL)
.fontSize(30)
.fontWeight(FontWeight.Bold)
Button('更新LocalStorage内容')
.onClick( ()=>{
this.countL++
})
TestChild()
}.width('100%')
}.height('100%')
}
}
@Component
export struct TestChild {
// @LocalStorageLink变量装饰器与LocalStorage中的'PropA'属性建立双向绑定
@StorageLink('PropA') count: number = 1;
build(){
Column( {space : 20} ) {
Text('HarmonyOS - Child' + this.count)
.fontSize(30)
.fontWeight(FontWeight.Bold)
Button('TestChild2 更新文字内容')
.onClick( ()=>{
this.count++
})
}.width('100%')
.backgroundColor(Color.Pink)
}
}
六、@Builder
@Builder
用于UI元素复用,开发者可以将重复使用的UI元素抽象成一个方法,在build
方法里调用。
总结
- 值引用方式,可以感知父组件的状态变化;
- 值传递方式,无法感知父组件的状态变化;
typescript
@Entry
@Component
struct Index {
@State count: number = 1;
@Builder BuilderOne($$: { paramA1: number }) {
Column() {
Text(`组件1值引用: ${$$.paramA1} `).fontSize(20)
}.width('100%').backgroundColor(Color.Pink)
}
@Builder BuilderTwo(paramA1: number) {
Column() {
Text(`组件2值传递: ${paramA1} `).fontSize(20)
}.width('100%').backgroundColor(Color.Pink)
}
build() {
Row() {
Column({ space: 20 }) {
Text('混沌 ' + this.count)
.fontSize(30)
.fontWeight(FontWeight.Bold)
Button('更新')
.onClick(() => {
this.count++
})
this.BuilderOne({ paramA1: this.count })
this.BuilderTwo(this.count)
}.width('100%')
}.height('100%')
}
}
七、@BuilderParam
当开发者创建了自定义组件,并想对该组件添加特定功能时,例如在自定义组件中添加一个点击跳转操作。若直接在组件内嵌入事件方法,将会导致所有引入该自定义组件的地方均增加了该功能。为解决此问题,ArkUI引入了@BuilderParam
装饰器,@BuilderParam
用来装饰指向@Builder
方法的变量,开发者可在初始化自定义组件时对此属性进行赋值,为自定义组件增加特定的功能。该装饰器用于声明任意UI描述的一个元素,类似slot
占位符。
typescript
import Prompt from '@system.prompt';
import { TestChild } from './TestChild';
@Entry
@Component
struct Index {
@Builder BuilderOne() {
TestChild( {msg: 'BuilderOne 视图'} ) {
Text('1').fontColor(Color.Red)
}
}
@Builder BuilderTwo() {
Stack(){
TestChild( {msg: 'BuilderTwo 视图'} ) {
Text('1').fontColor(Color.Red)
Text('2').fontColor(Color.Red)
}
}.onClick( () => {
Prompt.showToast({message: '点了 BuilderTwo'})
})
}
@BuilderParam aBuilder0: () => void = this.BuilderOne
@BuilderParam aBuilder1: () => void = this.BuilderTwo
build(){
Column({ space: 20 }) {
this.aBuilder0()
this.aBuilder1()
TestChild( {msg: '中国'} ) {
Text('1').fontColor(Color.Red)
})
}.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
@Component
export struct TestChild {
msg: string
@BuilderParam aB0: () => {}
build(){
Column( {space : 20} ) {
this.aB0()
Text('TestChild上下有 '+ this.msg)
.fontSize(20)
.fontWeight(FontWeight.Bold)
this.aB0()
}.width('100%')
.backgroundColor(Color.Pink)
}
}
@BuilderParam
既可以指向一个对象, 也可以指向@Builder
修饰的方法;
带占位的自定义视图是没法响应onClick事件的,所以在上面的示例中,将子组件外边再添加了一个容器组件,用来进行点击事件响应
八、@Styles
如果每个组件的样式都需要单独设置,在开发过程中会出现大量代码在进行重复样式设置,虽然可以复制粘贴,但为了代码简洁性和后续方便维护,可使用装饰器@Styles
。
typescript
import Prompt from '@system.prompt';
@Entry
@Component
struct Index {
//仅支持公共属性
@Styles fancy() {
.width(200)
.height(300)
.backgroundColor(Color.Pink)
.onClick(() => {
Prompt.showToast({message: 'I am fancy'})
})
}
build() {
Column({ space: 20 }) {
Text('Styles')
.textAlign(TextAlign.Center)
.fancy()
}.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
总结
@Styles
当前仅支持通用属性;@Styles
修饰的方法不支持参数;- 引用**@Styles** 修饰的方法时,建议放在最后,比如:
Text().fancy().textAlign(....)
应该变为Text().textAlign(....) .fancy()
九、@Extend
用于扩展原生组件样式。
注意⚠️:
- 原生指的是
ArkTS
写的组件扩展,不是新定义增加不存在的属性:
typescript
import Prompt from '@system.prompt';
//仅支持公共属性
@Styles function fancy() {
.width(200)
.height(300)
.backgroundColor(Color.Pink)
.onClick(() => {
Prompt.showToast({message: 'I am fancy'})
})
}
@Extend(Text) function superFancy(size:number, onClick?: () => void) {
.fontSize(size)
.textAlign(TextAlign.Center)
.fancy()
.onClick(onClick)
}
@Entry
@Component
struct Index {
onClickHandler() {
Prompt.showToast({message: 'fancy出去了'})
}
build(){
Column({ space: 20 }) {
Text('Styles')
.superFancy(30, this.onClickHandler.bind(this))
}.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
总结
@Extend
在@Styles
基础上,增加了传参特性;@Extend
必须定义为全局;- 支持封装指定组件的私有属性和私有事件和预定义相同组件的
@Extend
的方法;
十、@Concurrent
在使用TaskPool
时,执行的并发函数需要使用该装饰器修饰,否则无法通过相关校验。
typescript
import taskpool from '@ohos.taskpool';
@Concurrent
function add(num1: number, num2: number): number {
return num1 + num2;
}
async function ConcurrentFunc(): Promise<void> {
try {
let task: taskpool.Task = new taskpool.Task(add, 1, 2);
console.info("taskpool res is: " + await taskpool.execute(task));
}catch (e) {
}
}
@Entry
@Component
struct Index {
@State message: string = 'Hello World'
build(){
Row(){
Column(){
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
ConcurrentFunc();
})
}.width('100%')
}.height('100%')
}
}