1# \@Link装饰器:父子双向同步 2 3 4子组件中被\@Link装饰的变量与其父组件中对应的数据源建立双向数据绑定。 5 6 7> **说明:** 8> 9> 从API version 9开始,该装饰器支持在ArkTS卡片中使用。 10 11 12## 概述 13 14\@Link装饰的变量与其父组件中的数据源共享相同的值。 15 16 17## 限制条件 18 19- \@Link装饰器不能在\@Entry装饰的自定义组件中使用。 20 21 22## 装饰器使用规则说明 23 24| \@Link变量装饰器 | 说明 | 25| ------------------------------------------------------------ | ------------------------------------------------------------ | 26| 装饰器参数 | 无 | 27| 同步类型 | 双向同步。<br/>父组件中\@State, \@StorageLink和\@Link 和子组件\@Link可以建立双向数据同步,反之亦然。 | 28| 允许装饰的变量类型 | Object、class、string、number、boolean、enum类型,以及这些类型的数组。<br/>支持Date类型。<br/>API11及以上支持Map、Set类型。支持类型的场景请参考[观察变化](#观察变化)。<br/>API11及以上支持上述支持类型的联合类型,比如string \| number, string \| undefined 或者 ClassA \| null,示例见[Link支持联合类型实例](#link支持联合类型实例)。 <br/>**注意**<br/>当使用undefined和null的时候,建议显式指定类型,遵循TypeScipt类型校验,比如:`@Link a : string \| undefined`。 | 29| <br/>支持AkrUI框架定义的联合类型Length、ResourceStr、ResourceColor类型。<br/>类型必须被指定,且和双向绑定状态变量的类型相同。<br/>不支持any。 | | 30| 被装饰变量的初始值 | 无,禁止本地初始化。 | 31 32 33## 变量的传递/访问规则说明 34 35| 传递/访问 | 说明 | 36| ---------- | ---------------------------------------- | 37| 从父组件初始化和更新 | 必选。与父组件\@State, \@StorageLink和\@Link 建立双向绑定。允许父组件中\@State、\@Link、\@Prop、\@Provide、\@Consume、\@ObjectLink、\@StorageLink、\@StorageProp、\@LocalStorageLink和\@LocalStorageProp装饰变量初始化子组件\@Link。<br/>从API version 9开始,\@Link子组件从父组件初始化\@State的语法为Comp({ aLink: this.aState })。同样Comp({aLink: $aState})也支持。 | 38| 用于初始化子组件 | 允许,可用于初始化常规变量、\@State、\@Link、\@Prop、\@Provide。 | 39| 是否支持组件外访问 | 私有,只能在所属组件内访问。 | 40 41 **图1** 初始化规则图示 42 43 44 45 46## 观察变化和行为表现 47 48 49### 观察变化 50 51- 当装饰的数据类型为boolean、string、number类型时,可以同步观察到数值的变化,示例请参考[简单类型和类对象类型的@Link](#简单类型和类对象类型的link)。 52 53- 当装饰的数据类型为class或者Object时,可以观察到赋值和属性赋值的变化,即Object.keys(observedObject)返回的所有属性,示例请参考[简单类型和类对象类型的@Link](#简单类型和类对象类型的link)。 54 55- 当装饰的对象是array时,可以观察到数组添加、删除、更新数组单元的变化,示例请参考[数组类型的@Link](#数组类型的link)。 56 57- 当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口`setFullYear`, `setMonth`, `setDate`, `setHours`, `setMinutes`, `setSeconds`, `setMilliseconds`, `setTime`, `setUTCFullYear`, `setUTCMonth`, `setUTCDate`, `setUTCHours`, `setUTCMinutes`, `setUTCSeconds`, `setUTCMilliseconds` 更新Date的属性。 58 59```ts 60@Component 61struct DateComponent { 62 @Link selectedDate: Date; 63 64 build() { 65 Column() { 66 Button(`child increase the year by 1`).onClick(() => { 67 this.selectedDate.setFullYear(this.selectedDate.getFullYear() + 1) 68 }) 69 Button('child update the new date') 70 .margin(10) 71 .onClick(() => { 72 this.selectedDate = new Date('2023-09-09') 73 }) 74 DatePicker({ 75 start: new Date('1970-1-1'), 76 end: new Date('2100-1-1'), 77 selected: this.selectedDate 78 }) 79 } 80 81 } 82} 83 84@Entry 85@Component 86struct ParentComponent { 87 @State parentSelectedDate: Date = new Date('2021-08-08'); 88 89 build() { 90 Column() { 91 Button('parent increase the month by 1') 92 .margin(10) 93 .onClick(() => { 94 this.parentSelectedDate.setMonth(this.parentSelectedDate.getMonth() + 1) 95 }) 96 Button('parent update the new date') 97 .margin(10) 98 .onClick(() => { 99 this.parentSelectedDate = new Date('2023-07-07') 100 }) 101 DatePicker({ 102 start: new Date('1970-1-1'), 103 end: new Date('2100-1-1'), 104 selected: this.parentSelectedDate 105 }) 106 107 DateComponent({ selectedDate:this.parentSelectedDate }) 108 } 109 } 110} 111``` 112 113- 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口`set`, `clear`, `delete` 更新Map的值。详见[装饰Map类型变量](#装饰map类型变量)。 114 115- 当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口`add`, `clear`, `delete` 更新Set的值。详见[装饰Set类型变量](#装饰set类型变量)。 116 117### 框架行为 118 119\@Link装饰的变量和其所属的自定义组件共享生命周期。 120 121为了了解\@Link变量初始化和更新机制,有必要先了解父组件和拥有\@Link变量的子组件的关系,初始渲染和双向更新的流程(以父组件为\@State为例)。 122 1231. 初始渲染:执行父组件的build()函数后将创建子组件的新实例。初始化过程如下: 124 1. 必须指定父组件中的\@State变量,用于初始化子组件的\@Link变量。子组件的\@Link变量值与其父组件的数据源变量保持同步(双向数据同步)。 125 2. 父组件的\@State状态变量包装类通过构造函数传给子组件,子组件的\@Link包装类拿到父组件的\@State的状态变量后,将当前\@Link包装类this指针注册给父组件的\@State变量。 126 1272. \@Link的数据源的更新:即父组件中状态变量更新,引起相关子组件的\@Link的更新。处理步骤: 128 1. 通过初始渲染的步骤可知,子组件\@Link包装类把当前this指针注册给父组件。父组件\@State变量变更后,会遍历更新所有依赖它的系统组件(elementid)和状态变量(比如\@Link包装类)。 129 2. 通知\@Link包装类更新后,子组件中所有依赖\@Link状态变量的系统组件(elementId)都会被通知更新。以此实现父组件对子组件的状态数据同步。 130 1313. \@Link的更新:当子组件中\@Link更新后,处理步骤如下(以父组件为\@State为例): 132 1. \@Link更新后,调用父组件的\@State包装类的set方法,将更新后的数值同步回父组件。 133 2. 子组件\@Link和父组件\@State分别遍历依赖的系统组件,进行对应的UI的更新。以此实现子组件\@Link同步回父组件\@State。 134 135 136## 使用场景 137 138 139### 简单类型和类对象类型的\@Link 140 141以下示例中,点击父组件ShufflingContainer中的“Parent View: Set yellowButton”和“Parent View: Set GreenButton”,可以从父组件将变化同步给子组件。 142 143 1.点击子组件GreenButton和YellowButton中的Button,子组件会发生相应变化,将变化同步给父组件。因为@Link是双向同步,会将变化同步给@State。 144 145 2.当点击父组件ShufflingContainer中的Button时,@State变化,也会同步给@Link,子组件也会发生对应的刷新。 146 147```ts 148class GreenButtonState { 149 width: number = 0; 150 151 constructor(width: number) { 152 this.width = width; 153 } 154} 155 156@Component 157struct GreenButton { 158 @Link greenButtonState: GreenButtonState; 159 160 build() { 161 Button('Green Button') 162 .width(this.greenButtonState.width) 163 .height(40) 164 .backgroundColor('#64bb5c') 165 .fontColor('#FFFFFF,90%') 166 .onClick(() => { 167 if (this.greenButtonState.width < 700) { 168 // 更新class的属性,变化可以被观察到同步回父组件 169 this.greenButtonState.width += 60; 170 } else { 171 // 更新class,变化可以被观察到同步回父组件 172 this.greenButtonState = new GreenButtonState(180); 173 } 174 }) 175 } 176} 177 178@Component 179struct YellowButton { 180 @Link yellowButtonState: number; 181 182 build() { 183 Button('Yellow Button') 184 .width(this.yellowButtonState) 185 .height(40) 186 .backgroundColor('#f7ce00') 187 .fontColor('#FFFFFF,90%') 188 .onClick(() => { 189 // 子组件的简单类型可以同步回父组件 190 this.yellowButtonState += 40.0; 191 }) 192 } 193} 194 195@Entry 196@Component 197struct ShufflingContainer { 198 @State greenButtonState: GreenButtonState = new GreenButtonState(180); 199 @State yellowButtonProp: number = 180; 200 201 build() { 202 Column() { 203 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) { 204 // 简单类型从父组件@State向子组件@Link数据同步 205 Button('Parent View: Set yellowButton') 206 .width(312) 207 .height(40) 208 .margin(12) 209 .fontColor('#FFFFFF,90%') 210 .onClick(() => { 211 this.yellowButtonProp = (this.yellowButtonProp < 700) ? this.yellowButtonProp + 40 : 100; 212 }) 213 // class类型从父组件@State向子组件@Link数据同步 214 Button('Parent View: Set GreenButton') 215 .width(312) 216 .height(40) 217 .margin(12) 218 .fontColor('#FFFFFF,90%') 219 .onClick(() => { 220 this.greenButtonState.width = (this.greenButtonState.width < 700) ? this.greenButtonState.width + 100 : 100; 221 }) 222 // class类型初始化@Link 223 GreenButton({ greenButtonState: $greenButtonState }).margin(12) 224 // 简单类型初始化@Link 225 YellowButton({ yellowButtonState: $yellowButtonProp }).margin(12) 226 } 227 } 228 } 229} 230``` 231 232 233 234### 数组类型的\@Link 235 236 237```ts 238@Component 239struct Child { 240 @Link items: number[]; 241 242 build() { 243 Column() { 244 Button(`Button1: push`) 245 .margin(12) 246 .width(312) 247 .height(40) 248 .fontColor('#FFFFFF,90%') 249 .onClick(() => { 250 this.items.push(this.items.length + 1); 251 }) 252 Button(`Button2: replace whole item`) 253 .margin(12) 254 .width(312) 255 .height(40) 256 .fontColor('#FFFFFF,90%') 257 .onClick(() => { 258 this.items = [100, 200, 300]; 259 }) 260 } 261 } 262} 263 264@Entry 265@Component 266struct Parent { 267 @State arr: number[] = [1, 2, 3]; 268 269 build() { 270 Column() { 271 Child({ items: $arr }) 272 .margin(12) 273 ForEach(this.arr, 274 (item: number) => { 275 Button(`${item}`) 276 .margin(12) 277 .width(312) 278 .height(40) 279 .backgroundColor('#11a2a2a2') 280 .fontColor('#e6000000') 281 }, 282 (item: ForEachInterface) => item.toString() 283 ) 284 } 285 } 286} 287``` 288 289 290 291上文所述,ArkUI框架可以观察到数组元素的添加,删除和替换。在该示例中\@State和\@Link的类型是相同的number[],不允许将\@Link定义成number类型(\@Link item : number),并在父组件中用\@State数组中每个数据项创建子组件。如果要使用这个场景,可以参考[\@Prop](arkts-prop.md)和\@Observed。 292 293### 装饰Map类型变量 294 295> **说明:** 296> 297> 从API version 11开始,\@Link支持Map类型。 298 299在下面的示例中,value类型为Map\<number, string\>,点击Button改变message的值,视图会随之刷新。 300 301```ts 302@Component 303struct Child { 304 @Link value: Map<number, string> 305 306 build() { 307 Column() { 308 ForEach(Array.from(this.value.entries()), (item: [number, string]) => { 309 Text(`${item[0]}`).fontSize(30) 310 Text(`${item[1]}`).fontSize(30) 311 Divider() 312 }) 313 Button('child init map').onClick(() => { 314 this.value = new Map([[0, "a"], [1, "b"], [3, "c"]]) 315 }) 316 Button('child set new one').onClick(() => { 317 this.value.set(4, "d") 318 }) 319 Button('child clear').onClick(() => { 320 this.value.clear() 321 }) 322 Button('child replace the first one').onClick(() => { 323 this.value.set(0, "aa") 324 }) 325 Button('child delete the first one').onClick(() => { 326 this.value.delete(0) 327 }) 328 } 329 } 330} 331 332 333@Entry 334@Component 335struct MapSample2 { 336 @State message: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]]) 337 338 build() { 339 Row() { 340 Column() { 341 Child({ value: this.message }) 342 } 343 .width('100%') 344 } 345 .height('100%') 346 } 347} 348``` 349 350### 装饰Set类型变量 351 352> **说明:** 353> 354> 从API version 11开始,\@Link支持Set类型。 355 356在下面的示例中,message类型为Set\<number\>,点击Button改变message的值,视图会随之刷新。 357 358```ts 359@Component 360struct Child { 361 @Link message: Set<number> 362 363 build() { 364 Column() { 365 ForEach(Array.from(this.message.entries()), (item: [number, string]) => { 366 Text(`${item[0]}`).fontSize(30) 367 Divider() 368 }) 369 Button('init set').onClick(() => { 370 this.message = new Set([0, 1, 2, 3, 4]) 371 }) 372 Button('set new one').onClick(() => { 373 this.message.add(5) 374 }) 375 Button('clear').onClick(() => { 376 this.message.clear() 377 }) 378 Button('delete the first one').onClick(() => { 379 this.message.delete(0) 380 }) 381 } 382 .width('100%') 383 } 384} 385 386 387@Entry 388@Component 389struct SetSample1 { 390 @State message: Set<number> = new Set([0, 1, 2, 3, 4]) 391 392 build() { 393 Row() { 394 Column() { 395 Child({ message: this.message }) 396 } 397 .width('100%') 398 } 399 .height('100%') 400 } 401} 402``` 403 404## Link支持联合类型实例 405 406@Link支持联合类型和undefined和null,在下面的示例中,name类型为string | undefined,点击父组件Index中的Button改变name的属性或者类型,Child中也会对应刷新。 407 408```ts 409@Component 410struct Child { 411 @Link name: string | undefined 412 413 build() { 414 Column() { 415 416 Button('Child change name to Bob') 417 .onClick(() => { 418 this.name = "Bob" 419 }) 420 421 Button('Child change animal to undefined') 422 .onClick(() => { 423 this.name = undefined 424 }) 425 426 }.width('100%') 427 } 428} 429 430@Entry 431@Component 432struct Index { 433 @State name: string | undefined = "mary" 434 435 build() { 436 Column() { 437 Text(`The name is ${this.name}`).fontSize(30) 438 439 Child({ name: this.name }) 440 441 Button('Parents change name to Peter') 442 .onClick(() => { 443 this.name = "Peter" 444 }) 445 446 Button('Parents change name to undefined') 447 .onClick(() => { 448 this.name = undefined 449 }) 450 } 451 } 452} 453``` 454 455## 常见问题 456 457### \@Link装饰状态变量类型错误 458 459在子组件中使用\@Link装饰状态变量需要保证该变量与数据源类型完全相同,且该数据源需为被诸如\@State等装饰器装饰的状态变量。 460 461【反例】 462 463```ts 464@Observed 465class ClassA { 466 public c: number = 0; 467 468 constructor(c: number) { 469 this.c = c; 470 } 471} 472 473@Component 474struct LinkChild { 475 @Link testNum: number; 476 477 build() { 478 Text(`LinkChild testNum ${this.testNum}`) 479 } 480} 481 482@Entry 483@Component 484struct Parent { 485 @State testNum: ClassA[] = [new ClassA(1)]; 486 487 build() { 488 Column() { 489 Text(`Parent testNum ${this.testNum[0].c}`) 490 .onClick(() => { 491 this.testNum[0].c += 1; 492 }) 493 // @Link装饰的变量需要和数据源@State类型一致 494 LinkChild({ testNum: this.testNum[0].c }) 495 } 496 } 497} 498``` 499 500\@Link testNum: number从父组件的LinkChild({testNum:this.testNum.c})初始化。\@Link的数据源必须是装饰器装饰的状态变量,简而言之,\@Link装饰的数据必须和数据源类型相同,比如\@Link: T和\@State : T。所以,这里应该改为\@Link testNum: ClassA,从父组件初始化的方式为LinkChild({testNum: $testNum}) 501 502【正例】 503 504```ts 505@Observed 506class ClassA { 507 public c: number = 0; 508 509 constructor(c: number) { 510 this.c = c; 511 } 512} 513 514@Component 515struct LinkChild { 516 @Link testNum: ClassA[]; 517 518 build() { 519 Text(`LinkChild testNum ${this.testNum[0]?.c}`) 520 } 521} 522 523@Entry 524@Component 525struct Parent { 526 @State testNum: ClassA[] = [new ClassA(1)]; 527 528 build() { 529 Column() { 530 Text(`Parent testNum ${this.testNum[0].c}`) 531 .onClick(() => { 532 this.testNum[0].c += 1; 533 }) 534 // @Link装饰的变量需要和数据源@State类型一致 535 LinkChild({ testNum: $testNum }) 536 } 537 } 538} 539``` 540 541<!--no_check--> 542