# @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：用于监听状态变量的变化，当状态变量变化时，@Watch的回调方法将被调用。@Watch在ArkUI框架内部判断数值有无更新使用的是严格相等（===），遵循严格相等规范。当严格相等判断的结果是false（即不相等）的情况下，就会触发@Watch的回调。其常见使用场景可参考官方文档：使用场景。
*   AppStorage：与应用进程绑定的全局UI状态存储中心，由UI框架在应用启动时创建，将UI状态数据存储于运行内存，可以实现应用级全局状态共享。AppStorage可以通过AppStorage.setOrCreate方法存入数据，且可以通过@StorageLink和@StorageProp绑定状态变量，取出AppStorage内保存的值并使用。

#### 解决方案

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

| 场景 | 场景描述 | 解决方案 |
| --- | --- | --- |
| 场景一 | 如何解决单个状态变量绑定多个@Watch时，部分@Watch失效的问题？ | 将所有需要在状态变量变化时执行的多个函数，合并到一个函数中，再用@Watch在状态变量变化时一起调用。 |
| 场景二 | 如何解决@Watch装饰器监听AppStorage关联的状态变量时，@Watch失效的问题？ | 方案一：通过@StorageLink代替@StorageProp的单向同步，将this.mAccountInfo的修改同步回AppStorage，实现@Watch装饰器刷新。方案二：可以采用this.mAccountInfo = null代替AppStorage.setOrCreate('num', null)，该方式不会同步回AppStorage，但是可以触发@Watch刷新。 |
| 场景三 | 如何解决状态变量在父组件的一次点击事件中修改了两次，但是子组件@Watch只监听到一次修改的问题？ | 装饰器@Prop与@ObjectLink获取更新数据的时机较晚，导致子组件的状态变量只识别到一次变化，通过@Link装饰器接收即可。 |
| 场景四 | 如何解决@Watch无法监听嵌套对象属性的问题？ | 搭配@Observed/@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的触发时机。即：@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装饰器修饰的属性，若存在@Track装饰器修饰的属性，则未被@Track装饰器修饰的属性不会触发@Watch的监听。

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

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