@Watch监听生效触发规则与常见问题场景
=====================

#### 问题现象

*   **场景一**：如何解决单个状态变量绑定多个@Watch时，部分@Watch失效的问题？
    
    问题代码示例参考如下：
    
    @Entry
    @Component
    struct SceneOne {
      @State @Watch('changeOne') @Watch('changeTwo') num: number = 0;
    
     _ // 监听不生效_
      changeOne() {
        console.info('changeOne');
      }
    
     _ // 监听生效_
      changeTwo() {
        console.info('changeTwo');
      }
    
      build() {
        RelativeContainer() {
          Text(this.num.toString())
            .id('HelloWorld')
            .fontSize($r('app.float.page\_text\_font\_size'))
            .fontWeight(FontWeight.Bold)
            .alignRules({
              center: { anchor: '\_\_container\_\_', align: VerticalAlign.Center },
              middle: { anchor: '\_\_container\_\_', align: HorizontalAlign.Center }
            })
            .onClick(() => {
              this.num++;
            });
        }
        .height('100%')
        .width('100%');
      }
    }
    
*   **场景二**：如何解决@Watch装饰器监听AppStorage关联的状态变量时，@Watch失效的问题？
    
    问题代码示例参考如下（第二次修改为null时，不会触发@Watch，也不会刷新页面为null）：
    
    AppStorage.setOrCreate('num', 7);
    
    @Entry
    @Component
    struct SceneTwo {
      @Watch('change') @StorageProp('num') mAccountInfo: number | null = null;
    
      change() {
        console.info('变化了');
      }
    
      build() {
        Column({ space: 20 }) {
          Text(\`From AppStorage ${this.mAccountInfo}\`)
            .onClick(() => {
              if (this.mAccountInfo) {
                this.mAccountInfo++;
              } else {
                this.mAccountInfo = 1;
              }
            });
          Button('设置为null').onClick(() => {
            AppStorage.setOrCreate('num', null); _// 无法触发@Watch监听_
          });
        }.width('100%');
      }
    }
    
*   **场景三**：如何解决状态变量在父组件的一次点击事件中修改了两次，但是子组件@Watch只监听到一次修改的问题？
    
    问题代码示例参考如下：
    
    @Entry
    @Component
    struct SceneThree {
      @State @Watch('onValueChange') private value: string = '';
    
      private onValueChange() {
        console.info('SceneThree onValueChange %s', JSON.stringify(this.value));
      }
    
      build() {
        Column() {
          Button('连续修改状态变量')
            .onClick(() => {
              this.value = '1';
              this.value = '2';
            });
          SceneThreeChild({ value: this.value });
        };
      }
    }
    
    @Component
    struct SceneThreeChild {
      @Prop @Watch('onValueChange') value: string;
    
     _ // 父组件中修改了两次，但是子组件中onValueChange()函数只执行了一次。_
      private onValueChange() {
        console.info('SceneThreeChild onValueChange %s', JSON.stringify(this.value));
      }
    
      build() {
        Text(\`SceneThreeChild ${JSON.stringify(this.value)}\`);
      }
    }
    
*   **场景四**：如何解决@Watch无法监听嵌套对象属性的问题？
    
    问题代码示例参考如下：
    
    @Entry
    @Component
    struct SceneFour {
      @State @Watch('onValueChange') private test: Test = new Test();
    
      onValueChange() {
        console.info('onValueChange %s', JSON.stringify(this.test));
      }
    
      build() {
        Column() {
        _  // @Watch无法监听value属性的修改_
          Button('修改value属性')
            .onClick(() => {
              this.test.testChild.value += '1';
            });
          Button('修改key属性')
            .onClick(() => {
              this.test.key += '1';
            });
        };
      }
    }
    
    class Test {
      key: string = '';
      testChild: TestChild = new TestChild();
    }
    
    class TestChild {
      value: string = '';
    }
    

#### 背景知识

*   [@Watch](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-watch)：用于监听状态变量的变化，当状态变量变化时，@Watch的回调方法将被调用。@Watch在ArkUI框架内部判断数值有无更新使用的是严格相等（===），遵循严格相等规范。当严格相等判断的结果是false（即不相等）的情况下，就会触发@Watch的回调。其常见使用场景可参考官方文档：[使用场景](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-watch#使用场景)。
*   [AppStorage](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-appstorage)：与应用进程绑定的全局UI状态存储中心，由UI框架在应用启动时创建，将UI状态数据存储于运行内存，可以实现应用级全局状态共享。AppStorage可以通过[AppStorage.setOrCreate](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-state-management#setorcreate10)方法存入数据，且可以通过[@StorageLink](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-appstorage#storagelink)和[@StorageProp](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-appstorage#storageprop)绑定状态变量，取出AppStorage内保存的值并使用。

#### 解决方案

@Watch监听时常见问题场景及解决方案总结如下：

| 
场景

 | 

场景描述

 | 

解决方案

 |
| :-- | :-- | :-- |
| 

场景一

 | 

如何解决单个状态变量绑定多个@Watch时，部分@Watch失效的问题？

 | 

将所有需要在状态变量变化时执行的多个函数，合并到一个函数中，再用@Watch在状态变量变化时一起调用。

 |
| 

场景二

 | 

如何解决@Watch装饰器监听AppStorage关联的状态变量时，@Watch失效的问题？

 | 

1.  方案一：通过@StorageLink代替@StorageProp的单向同步，将this.mAccountInfo的修改同步回AppStorage，实现@Watch装饰器刷新。
2.  方案二：可以采用this.mAccountInfo = null代替AppStorage.setOrCreate('num', null)，该方式不会同步回AppStorage，但是可以触发@Watch刷新。

 |
| 

场景三

 | 

如何解决状态变量在父组件的一次点击事件中修改了两次，但是子组件@Watch只监听到一次修改的问题？

 | 

装饰器@Prop与@ObjectLink获取更新数据的时机较晚，导致子组件的状态变量只识别到一次变化，通过@Link装饰器接收即可。

 |
| 

场景四

 | 

如何解决@Watch无法监听嵌套对象属性的问题？

 | 

搭配[@Observed/@ObjectLink](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-observed-and-objectlink)装饰器在对应的子组件中使用@Watch监听深层属性的变化。

 |

*   **场景一**：如何解决单个状态变量绑定多个@Watch时，部分@Watch失效的问题？
    
    状态变量的变化无法被多个@Watch同时监听。因此，需要将所有需要在状态变量变化时执行的多个函数，合并到一个函数中，再用一个@Watch装饰器监听，并执行该合并后的回调函数，完整示例参考如下：
    
    @Entry
    @Component
    struct SceneOne {
      @State @Watch('changeOne') num: number = 0;
    
      changeOne() {
       _ // 将多个需要被@Watch执行的函数合并为一个函数_
        console.info('changeOne');
        console.info('changeTwo');
      }
    
      build() {
        RelativeContainer() {
          Text(this.num.toString())
            .id('HelloWorld')
            .fontSize($r('app.float.page\_text\_font\_size'))
            .fontWeight(FontWeight.Bold)
            .alignRules({
              center: { anchor: '\_\_container\_\_', align: VerticalAlign.Center },
              middle: { anchor: '\_\_container\_\_', align: HorizontalAlign.Center }
            })
            .onClick(() => {
              this.num++;
            });
        }
        .height('100%')
        .width('100%');
      }
    }
    
*   **场景二**：如何解决@Watch装饰器监听AppStorage关联的状态变量时，@Watch失效的问题？
    
    该场景下，@Watch监听失效的原因如下：
    
    1.  场景二问题代码中通过@StorageProp装饰状态变量，该变量无法同步修改AppStorage，从而导致第一次执行AppStorage.setOrCreate('num', null)后，再修改this.mAccountInfo时并没有同步修改回AppStorage内。
    2.  此时AppStorage内的值一直为null，通过AppStorage.setOrCreate再次设置为null时，由于AppStorage没有修改，也不会通知this.mAccountInfo更新，所以导致无法触发@Watch监听。
        
        针对以上原因分析有以下两种解决方案：
        
        *   **方案一**：通过@StorageLink代替@StorageProp的单向同步，将this.mAccountInfo的修改同步回AppStorage，实现@Watch装饰器刷新。完整示例参考如下：
            
            AppStorage.setOrCreate('num', 7);
            
            @Entry
            @Component
            struct SceneTwoOptionOne {
              @Watch('change') @StorageLink('num') mAccountInfo: number | null = null;
            
              change() {
                console.info('变化了');
              }
            
              build() {
                Column({ space: 20 }) {
                  Text(\`From AppStorage ${this.mAccountInfo}\`)
                    .onClick(() => {
                      if (this.mAccountInfo) {
                        this.mAccountInfo++;
                      } else {
                        this.mAccountInfo = 1;
                      }
                    });
                  Button('设置为null').onClick(() => {
                    AppStorage.setOrCreate('num', null);
                  });
                }.width('100%');
              }
            }
            
        *   **方案二**：若不需要将修改的值同步修改回AppStorage，只是需要触发@Watch监听，可以直接采用this.mAccountInfo = null代替AppStorage.setOrCreate('num', null)。完整示例参考如下：
            
            AppStorage.setOrCreate('num', 7);
            
            @Entry
            @Component
            struct SceneTwoOptionTwo {
              @Watch('change') @StorageProp('num') mAccountInfo: number | null = null;
            
              change() {
                console.info('变化了');
              }
            
              build() {
                Column({ space: 20 }) {
                  Text(\`From AppStorage ${this.mAccountInfo}\`)
                    .onClick(() => {
                      if (this.mAccountInfo) {
                        this.mAccountInfo++;
                      } else {
                        this.mAccountInfo = 1;
                      }
                    });
                  Button('设置为null').onClick(() => {
                    this.mAccountInfo = null;
                  });
                }.width('100%');
              }
            }
            
*   **场景三**：如何解决状态变量在父组件的一次点击事件中修改了两次，但是子组件@Watch只监听到一次修改的问题？
    
    该问题与@Watch触发的时效性有关，详情可参考官网：[@Watch的触发时机](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-watch#watch的触发时机)。即：@Prop与@ObjectLink获取更新数据的时机较晚，导致子组件的状态变量只识别到一次变化，通过@Link装饰器接收即可。
    
    完整示例参考如下：
    
    @Entry
    @Component
    struct SceneThree {
      @State @Watch('onValueChange') private value: string = '';
    
      private onValueChange() {
        console.info('SceneThree onValueChange %s', JSON.stringify(this.value));
      }
    
      build() {
        Column() {
          Button('连续修改状态变量')
            .onClick(() => {
              this.value = '1';
              this.value = '2';
            });
          SceneThreeChild({ value: this.value });
        };
      }
    }
    
    @Component
    struct SceneThreeChild {
      @Link @Watch('onValueChange') value: string;
    
     _ // 父组件中修改了两次，但是子组件中onValueChange()函数也可以执行两次_
      private onValueChange() {
        console.info('SceneThreeChild onValueChange %s', JSON.stringify(this.value));
      }
    
      build() {
        Text(\`SceneThreeChild ${JSON.stringify(this.value)}\`);
      }
    }
    
*   **场景四**：如何解决@Watch无法监听嵌套对象属性的问题？
    
    搭配@Observed/@ObjectLink装饰器在对应的子组件中使用@Watch监听深层属性的变化，完整示例参考如下：
    
    @Entry
    @Component
    struct SceneFour {
      @State @Watch('onValueChange') test: Test = new Test();
    
      onValueChange() {
        console.info('SceneFour onValueChange %s', JSON.stringify(this.test));
      }
    
      build() {
        Column() {
          Button('SceneFour 修改key属性')
            .onClick(() => {
              this.test.key += '1';
            });
          SceneFourChild({ testChild: this.test.testChild });
        };
      }
    }
    
    @Component
    struct SceneFourChild {
      @ObjectLink @Watch('onValueChange') testChild: TestChild;
    
      onValueChange() {
        console.info('SceneFourChild onValueChange %s', JSON.stringify(this.testChild));
      }
    
      build() {
        Column() {
       _   // @Watch在子组件中监听深层属性变化_
          Button('SceneFourChild 修改value属性')
            .onClick(() => {
              this.testChild.value += '1';
            });
        };
      }
    }
    
    @Observed
    class Test {
      key: string = '';
      testChild: TestChild = new TestChild();
    }
    
    @Observed
    class TestChild {
      value: string = '';
    }
    

#### 常见FAQ

Q：如何解决@Watch报错Cannot find name 'xxx'的问题？

A：@Watch('xxx')内的xxx函数不存在，需要检查函数是否存在，同时检查函数名是否存在书写错误。

Q：@Watch是否可以监听@Prop、@Provide装饰器装饰的状态变量？

A：@Watch可以监听@Prop、@Provide装饰器装饰的状态变量的修改。

Q：如何解决@Watch监听对象时，@Watch监听失效的问题？

A：当@Watch监听的对象是状态变量时，该对象的第一层属性变化可以触发@Watch监听，若触发失效可按照以下步骤检查：

1.  是否存在场景四中监听嵌套对象，导致的失效问题。
2.  对象中是否存在[@Track](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-track)装饰器修饰的属性，若存在@Track装饰器修饰的属性，则未被@Track装饰器修饰的属性不会触发@Watch的监听。

Q：@Watch是否可以监听状态变量修改为null，或从null修改为其它值？

A：可以。@Watch在ArkUI框架内部判断数值有无更新使用的是严格相等（===）。