# @Provide装饰器和@Consume装饰器：与后代组件双向同步

@Provide和@Consume，应用于与后代组件的双向数据同步、状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递，@Provide和@Consume摆脱参数传递机制的束缚，实现跨层级传递。

其中@Provide装饰的变量是在祖先组件中，可以理解为被“提供”给后代的状态变量。@Consume装饰的变量是在后代组件中，去“消费（绑定）”祖先组件提供的变量。

@Provide/@Consume是跨组件层级的双向同步。在阅读@Provide和@Consume文档前，建议开发者对UI范式基本语法和自定义组件有基本的了解。建议提前阅读：基本语法概述，声明式UI描述，创建自定义组件。最佳实践请参考状态管理最佳实践。常见问题请参考状态管理常见问题。

从API version 9开始，这两个装饰器支持在ArkTS卡片中使用。

从API version 11开始，这两个装饰器支持在元服务中使用。

API version 19及以前，@Provide和@Consume双向同步仅支持声明式节点场景。

从API version 20开始，@Consume装饰的变量支持设置默认值。当查找不到@Provide的匹配结果时，@Consume装饰的变量会使用默认值进行初始化；当查找到@Provide的匹配结果时，@Consume装饰的变量会优先使用@Provide匹配结果的值，默认值不生效。

从API version 20开始，通过配置BuilderNode的BuildOptions参数enableProvideConsumeCrossing为true，使得@Provide和@Consume支持跨BuilderNode双向同步。但需要注意，BuilderNode会在上树前构造节点，所以BuilderNode内部定义的@Consume需要设置默认值，并在BuilderNode上树后，重新获取最近的@Provide数据，与之建立双向同步关系。具体可见@Consume在跨BuilderNode场景下和@Provide建立双向同步。

#### 概述

@Provide/@Consume装饰的状态变量有以下特性：

*   @Provide装饰的状态变量自动对其所有后代组件可用，开发者不需要多次在组件之间传递变量。
    
*   后代通过使用@Consume获取@Provide提供的变量，建立在@Provide和@Consume之间的双向数据同步，与@State/@Link不同的是，前者可以更便捷的在多层级父子组件之间传递。
    
*   @Provide和@Consume通过变量名或者变量别名绑定，需要类型相同，否则会发生类型隐式转换，从而导致应用行为异常。
    

```
// 通过相同的变量名绑定
@Provide age: number = 0;
@Consume age: number;
   
// 通过相同的变量别名绑定
@Provide('a') id: number = 0;
@Consume('a') age: number;
   
// 通过Provide的变量别名和Consume的变量名相同绑定
@Provide('a') id: number = 0;
@Consume a: number;
   
// 通过Provide的变量名和Consume的变量别名绑定
@Provide id: number = 0;
@Consume('id') a: number;
```

当@Provide指定变量别名时，会同时保存变量名与变量别名，@Consume在查找时，会优先以变量别名作为查找值去匹配，如果没有别名则用变量名作为查找值，只要@Consume提供的查找值与@Provide保存的变量名或别名中任意一项一致，即可成功建立绑定关系。

#### 装饰器说明

| @Provide变量装饰器 | 说明 |
| --- | --- |
| 装饰器参数 | 别名：常量字符串，可选。 如果指定了别名，则通过别名来绑定变量；如果未指定别名，则通过变量名绑定变量。 allowOverride：允许重写，string类型，可选。 如果使用allowOverride指定别名，则别名可以被重写，即可以存在同名的@Provide变量。 未使用allowOverride时则不允许重名。示例见@Provide支持allowOverride参数。 |
| 允许装饰的变量类型 | Object、class、string、number、boolean、enum类型，以及这些类型的数组。 API version 10开始支持Date类型。 API version 11及以上支持Map、Set类型、undefined和null类型、ArkUI框架定义的联合类型Length、ResourceStr、ResourceColor类型以及这些类型的联合类型，示例见@Provide和Consume支持联合类型实例。 |
| 不允许装饰的变量类型 | 不支持装饰Function类型。 |
| 初始化规则 | 必须定义本地默认值。 可以从父组件传入非undefined类型变量，此时使用该传入变量进行初始化。 父组件未传入或传入undefined类型变量时，使用本地默认值进行初始化。 |
| 同步规则 | 在子组件使用时： 不与父组件中的任何类型变量同步。 父组件传入的外部变量对@Provide初始化时，仅作为初始值，后续变量的变化不会同步至@Provide。 在父组件使用时： 可以初始化子组件的常规变量、@State、@Link、@Prop、@Provide。 @Provide变量的变化会同步给子组件的@Link、@Prop变量。 与后代子组件中别名匹配的@Consume变量双同步。 |

**图1** @Provide初始化规则图示

| @Consume变量装饰器 | 说明 |
| --- | --- |
| 装饰器参数 | 别名：常量字符串，可选。 如果指定了别名，则通过别名来绑定变量；如果未指定别名，则通过变量名绑定变量。 |
| 允许装饰的变量类型 | Object、class、string、number、boolean、enum类型，以及这些类型的数组。 API version 10开始支持Date类型。 API version 11及以上支持Map、Set类型、undefined和null类型、ArkUI框架定义的联合类型Length、ResourceStr、ResourceColor类型以及这些类型的联合类型，示例见@Provide和Consume支持联合类型实例。 说明： API version 20之前，@Consume装饰的变量，在其父组件或者祖先组件上，必须有对应的属性和别名的@Provide装饰的变量。 |
| 不允许装饰的变量类型 | 不支持装饰Function类型。 |
| 初始化规则 | API version 20之前，@Consume装饰的变量不支持本地设置默认值，必须要有与其匹配的@Provide装饰的变量。 从API version 20开始，@Consume支持设置默认值。若存在匹配成功的@Provide，则会使用@Provide的变量值作为初始值。若未匹配到@Provide变量，则使用本地默认值。示例见@Consume装饰的变量支持设置默认值。 |
| 同步规则 | 在子组件使用时： 与祖先组件匹配的@Provide变量双向同步。 在父组件使用时： 可以初始化子组件的常规变量、@State、@Link、@Prop、@Provide。 @Consume变量的变化会同步给子组件的@Link、@Prop变量。 |

**图2** @Consume初始化规则图示

#### 观察变化和行为表现

#### 观察变化

*   当装饰的数据类型为boolean、string、number类型时，可以观察到数值的变化。
    
*   当装饰的数据类型为class或者Object的时候，可以观察到赋值和属性赋值的变化（属性为Object.keys(observedObject)返回的所有属性）。
    
*   当装饰Array时，可以观察到数组本身、数组项的赋值及其API操作带来的变化。详见装饰Array类型变量。
    
*   当装饰的对象是Date时，可以观察到Date整体的赋值，同时可通过调用Date的接口setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds 更新Date的属性，详见装饰Date类型变量。
    
*   当装饰的变量是Map时，可以观察到Map整体的赋值，同时可通过调用Map的接口set, clear, delete 更新Map的值。详见装饰Map类型变量。
    
*   当装饰的变量是Set时，可以观察到Set整体的赋值，同时可通过调用Set的接口add, clear, delete 更新Set的值。详见装饰Set类型变量。
    

#### 框架行为

1.  初始渲染：
    
    1.  @Provide装饰的变量会以Map的形式，传递给当前@Provide所属组件的所有子组件。
    2.  子组件中如果使用@Consume变量，则会在Map中查找是否有该变量名/alias（别名）对应的@Provide的变量。在API version 20之前，如果查找不到，框架会抛出JS ERROR。从API version 20开始，如果查找不到，会判断@Consume装饰的变量是否设置了默认值，如果没有设置默认值，框架会抛出JS ERROR。
    3.  在初始化@Consume变量时，如果在Map中有该变量名/alias（别名）对应的@Provide的变量，则和@State/@Link的流程类似，@Consume变量会在Map中查找到对应的@Provide变量进行保存，并把自己注册给@Provide。
    4.  从API version 20开始，在初始化@Consume变量时，如果在Map中没有该变量名/alias（别名）对应的@Provide的变量，而@Consume的变量设置了默认值时，@Consume变量会利用默认值创建一个临时的数据源，保证通知链路的连续性。
2.  当@Provide装饰的数据变化时：
    
    1.  通过初始渲染的步骤可知，子组件@Consume已把自己注册给父组件。父组件@Provide变量变更后，会遍历更新所有依赖它的系统组件（elementid）和状态变量（@Consume）。
    2.  通知@Consume更新后，子组件所有依赖@Consume的系统组件（elementId）都会被通知更新。以此实现@Provide对@Consume状态数据同步。
3.  当@Consume装饰的数据变化时：
    
    通过初始渲染的步骤可知，子组件@Consume持有@Provide的实例。在@Consume更新后调用@Provide的更新方法，将更新的数值同步回@Provide，以此实现@Consume向@Provide的同步更新。
    

#### 限制条件

1.  @Provide/@Consume的参数key必须为string类型，否则编译时会报错。
    
    ```
    // 错误写法，编译报错
    let change: number = 10;
    @Provide(change) message: string = 'Hello';
    
    // 正确写法
    let change: string = 'change';
    @Provide(change) message: string = 'Hello';
    ```
    
2.  @Consume装饰的变量不能在构造参数中传入初始化，否则编译时会报错。@Consume仅能通过key来匹配对应的@Provide变量或者从API version 20开始设置默认值进行初始化。
    
    【反例】
    
    ```
    @Component
    struct Child {
      @Consume msg: string;
    
      build() {
        Text(this.msg)
      }
    }
    
    @Entry
    @Component
    struct Parent {
      @Provide message: string = 'Hello';
    
      build() {
        Column() {
          // 错误写法，不允许外部传入初始化
          Child({msg: 'Hello'})
        }
      }
    }
    ```
    
    【正例】
    
    ```
    @Component
    struct Child {
      @Consume num: number;
      // 从API version 20开始，@Consume装饰的变量支持设置默认值
      @Consume num1: number = 17;
    
      build() {
        Column() {
          Text(`Value of num: ${this.num}`)
          Text(`Value of num1: ${this.num1}`)
        }
      }
    }
    
    @Entry
    @Component
    struct Parent {
      @Provide num: number = 10;
    
      build() {
        Column() {
          Text(`Value of num: ${this.num}`)
          Child()
        }
      }
    }
    ```
    
3.  @Provide的key重复定义时，框架会抛出运行时错误，从API version 23开始，将返回错误码140114，提醒开发者重复定义key。如果开发者需要重复key，可以使用allowOverride。
    
    ```
    // 错误写法，a重复定义
    @Provide('a') count: number = 10;
    @Provide('a') num: number = 10;
    
    // 正确写法
    @Provide('a') count: number = 10;
    @Provide('b') num: number = 10;
    ```
    
4.  在API version 20之前，初始化@Consume变量时，如果开发者没有定义对应key的@Provide变量，框架会抛出运行时错误，提示开发者初始化@Consume变量失败，原因是无法找到其对应key的@Provide变量。从API version 20开始，初始化@Consume变量时，如果开发者没有定义对应key的@Provide变量，同时没有设置默认值，框架会抛出运行时错误，从API version 23开始，将返回错误码140112，提示开发者初始化@Consume变量失败，原因是无法找到其对应key的@Provide变量同时也没有设置默认值。
    
    【反例】
    
    ```
    @Component
    struct Child {
      @Consume num: number;
    
      build() {
        Column() {
          Text(`num的值: ${this.num}`)
        }
      }
    }
    
    @Entry
    @Component
    struct Parent {
      // 错误写法，缺少@Provide
      num: number = 10;
    
      build() {
        Column() {
          Text(`num的值: ${this.num}`)
          Child()
        }
      }
    }
    ```
    
    【正例】
    
    ```
    @Component
    struct Child {
      @Consume num: number;
      // 正确写法 从API version 20开始，@Consume装饰的变量支持设置默认值
      @Consume numWithDefaultValue: number = 6;
    
      build() {
        Column() {
          Text(`Value of num: ${this.num}`)
          Text(`Value of numWithDefaultValue: ${this.numWithDefaultValue}`)
        }
      }
    }
    
    @Entry
    @Component
    struct Parent {
      // 正确写法
      @Provide num: number = 10;
    
      build() {
        Column() {
          Text(`Value of num: ${this.num}`)
          Child()
        }
      }
    }
    ```
    
5.  @Provide与@Consume不支持装饰Function类型的变量，API version 23之前，框架会抛出运行时错误。
    
    从API version 23开始，添加对@Provide与@Consume装饰Function类型变量的校验，编译期会报错。
    
6.  从API version 20开始，支持跨BuilderNode配对@Provide/@Consume。在BuilderNode上树时，@Consume通过key匹配找到最近的@Provide，两者类型需要一致，如果不一致，则会抛出运行时错误。
    
    需要注意类型不相等判断，包括类实例的判断，比如：
    
    ```
    class A {}
    class B {}
    // 两个message都为object类型，但其构造函数不同，属于不同类型
    @Provide message: A = new A();
    @Consume message: B = new B();
    ```
    
    在非BuilderNode场景中，仍建议配对的@Provide/@Consume类型一致。虽然在运行时不会有强校验，但在@Consume装饰的变量初始化时，会隐式转换成@Provide装饰变量的类型。
    
    ```
    import { NodeController, BuilderNode, FrameNode, UIContext } from '@kit.ArkUI';
    
    @Builder
    function buildText() {
      Column() {
        Child()
      }
    }
    
    class TextNodeController extends NodeController {
      private builderNode: BuilderNode<[]> | null = null;
    
      constructor() {
        super();
      }
    
      makeNode(context: UIContext): FrameNode | null {
        this.builderNode = new BuilderNode(context);
        // 配置跨BuilderNode支持@Provide/@Consume
        this.builderNode.build(wrapBuilder(buildText), undefined,
          { enableProvideConsumeCrossing: true });
        // 将BuilderNode的根节点挂载到NodeContainer
        return this.builderNode.getFrameNode();
      }
    }
    
    @Entry
    @Component
    struct Index {
      @Provide message: string = 'hello';
      controller: TextNodeController = new TextNodeController();
    
      build() {
        Column() {
          NodeContainer(this.controller)
            .width('100%')
            .height(100)
        }
        .width('100%')
        .height('100%')
      }
    }
    
    
    @Component
    struct Child {
      // Child通过BuilderNode上树后，@Consume和Index中的@Provide建立连接时发现类型不一致，抛出运行时错误
      @Consume message: number = 0;
    
      build() {
        Column() {
          Text(`@Consume ${this.message}`)
        }
      }
    }
    ```
    
7.  父组件传入undefined时，@Provide装饰的变量仍使用本地默认值进行初始化。
    
    ```
    @Entry
    @Component
    struct Parent {
      @State count: number | undefined = undefined;
    
      build() {
        Column() {
          Text(`Parent count value: ${this.count}`)
            .fontSize(20)
            .margin(10)
          Child({ count: this.count })
        }
      }
    }
    
    @Component
    struct Child {
      // 父组件传入undefined时，@Provide装饰的变量仍使用本地默认值进行初始化
      @Provide count: number | undefined = 0;
    
      build() {
        Column() {
          Text(`Child count value: ${this.count}`)
            .fontSize(20)
            .margin(10)
        }
      }
    }
    ```
    

#### 使用场景

#### @Provide变量与@Consume变量建立双向绑定

以下示例是@Provide变量与后代组件中@Consume变量进行双向同步的场景。当分别点击ToDo和ToDoItem组件内的Button时，count的更改会双向同步在ToDo和ToDoItem中。

```
@Component
struct ToDoItem {
  // @Consume装饰的变量通过相同的属性名绑定其祖先组件ToDo内的@Provide装饰的变量
  @Consume count: number;

  build() {
    Column() {
      Text(`count(${this.count})`)
      Button(`count(${this.count}), count + 1`)
        .onClick(() => this.count += 1)
    }
    .width('50%')
  }
}

@Component
struct ToDoList {
  build() {
    Row({ space: 5 }) {
      ToDoItem()
      ToDoItem()
    }
  }
}

@Component
struct ToDoDemo {
  build() {
    ToDoList()
  }
}

@Entry
@Component
struct ToDo {
  // @Provide装饰的变量count由入口组件ToDo提供其后代组件
  @Provide count: number = 0;

  build() {
    Column() {
      Button(`count(${this.count}), count + 1`)
        .onClick(() => this.count += 1)
      ToDoDemo()
    }
  }
}
```

#### 装饰Array类型变量

以下示例中，message类型为number\[\]，点击Button改变message的值，视图会随之刷新。

```
@Entry
@Component
struct Index {
  @Provide message: number[] = [0, 1, 2, 3];

  build() {
    Column() {
      ForEach(this.message, (item: number) => {
        Text(`Provide ${item}`)
          .fontSize(20)
          .margin(10)
      })
      // 新增数组元素，触发UI刷新
      Button('Push element')
        .onClick(() => {
          this.message.push(4);
        })
        .width(300)
        .margin(10)
      // 删除数组元素，触发UI刷新
      Button('Pop element')
        .onClick(() => {
          this.message.pop();
        })
        .width(300)
        .margin(10)
      Child()
    }
  }
}

@Component
struct Child {
  @Consume message: number[] = [0, 1, 2, 3];

  build() {
    Row() {
      Column() {
        ForEach(this.message, (item: number) => {
          Text(`Consume ${item}`)
            .fontSize(20)
            .margin(10)
        })
        // 对数组整体重新赋值，触发UI刷新
        Button('Reset array')
          .onClick(() => {
            this.message = [9, 8, 7, 6];
          })
          .width(300)
          .margin(10)
        // 更新数组元素，触发UI刷新
        Button('Modify element[0]')
          .onClick(() => {
            this.message[0] = 10;
          })
          .width(300)
          .margin(10)
      }
      .width('100%')
    }
  }
}
```

#### 装饰Map类型变量

从API version 11开始，@Provide，@Consume支持Map类型。

以下示例中，message类型为Map<number, string>，点击Button改变message的值，视图会随之刷新。

```
@Component
struct Child {
  @Consume message: Map<number, string>

  build() {
    Column() {
      ForEach(Array.from(this.message.entries()), (item: [number, string]) => {
        Text(`${item[0]}`)
          .fontSize(30)
        Text(`${item[1]}`)
          .fontSize(30)
        Divider()
      })
      // message被@Consume装饰，可以被观察到Map整体的赋值以及调用Map接口带来的变化
      Button('Consume init Map')
        .onClick(() => {
          this.message = new Map([[0, 'a'], [1, 'b'], [3, 'c']]);
        })
      Button('Consume set new one')
        .onClick(() => {
          this.message.set(4, 'd');
        })
      Button('Consume clear')
        .onClick(() => {
          this.message.clear();
        })
      Button('Consume replace the first item')
        .onClick(() => {
          this.message.set(0, 'aa');
        })
      Button('Consume delete the first item')
        .onClick(() => {
          this.message.delete(0);
        })
    }
  }
}

@Entry
@Component
struct MapSample {
  @Provide message: Map<number, string> = new Map([[0, 'a'], [1, 'b'], [3, 'c']])

  build() {
    Row() {
      Column() {
        // message被@Provide装饰，可以被观察到Map整体的赋值以及调用Map接口带来的变化
        Button('Provide init Map')
          .onClick(() => {
            this.message = new Map([[0, 'a'], [1, 'b'], [3, 'c'], [4, 'd']]);
          })
        Child()
      }
      .width('100%')
    }
    .height('100%')
  }
}
```

#### 装饰Set类型变量

从API version 11开始，@Provide，@Consume支持Set类型。

以下示例中，message类型为Set<number>，点击Button改变message的值，视图会随之刷新。

```
@Component
struct Child {
  @Consume message: Set<number>

  build() {
    Column() {
      ForEach(Array.from(this.message.entries()), (item: [number, number]) => {
        Text(`${item[0]}`)
          .fontSize(30)
        Divider()
      })
      // message被@Consume装饰，可以被观察到Set整体的赋值以及调用Set接口带来的变化
      Button('Consume init set')
        .onClick(() => {
          this.message = new Set([0, 1, 2, 3, 4]);
        })
      Button('Consume set new one')
        .onClick(() => {
          this.message.add(5);
        })
      Button('Consume clear')
        .onClick(() => {
          this.message.clear();
        })
      Button('Consume delete the first one')
        .onClick(() => {
          this.message.delete(0);
        })
    }
    .width('100%')
  }
}

@Entry
@Component
struct SetSample {
  @Provide message: Set<number> = new Set([0, 1, 2, 3, 4])

  build() {
    Row() {
      Column() {
        // message被@Provide装饰，可以被观察到Set整体的赋值以及调用Set接口带来的变化
        Button('Provide init set')
          .onClick(() => {
            this.message = new Set([0, 1, 2, 3, 4, 5]);
          })
        Child()
      }
      .width('100%')
    }
    .height('100%')
  }
}
```

#### 装饰Date类型变量

以下示例中，selectedDate类型为Date，点击Button改变selectedDate的值，视图会随之刷新。

```
@Component
struct Child {
  @Consume selectedDate: Date;

  build() {
    Column() {
      // selectedDate被@Consume装饰，可以被观察到Date整体的赋值以及调用Date接口带来的变化
      Button(`child increase the day by 1`)
        .onClick(() => {
          this.selectedDate.setDate(this.selectedDate.getDate() + 1);
        })
      Button('child update the new date')
        .margin(10)
        .onClick(() => {
          this.selectedDate = new Date('2023-09-09');
        })
      DatePicker({
        start: new Date('1970-1-1'),
        end: new Date('2100-1-1'),
        selected: this.selectedDate
      })
    }
  }
}

@Entry
@Component
struct Parent {
  @Provide selectedDate: Date = new Date('2021-08-08')

  build() {
    Column() {
      // selectedDate被@Provide装饰，可以被观察到Date整体的赋值以及调用Date接口带来的变化
      Button('parent increase the day by 1')
        .margin(10)
        .onClick(() => {
          this.selectedDate.setDate(this.selectedDate.getDate() + 1);
        })
      Button('parent update the new date')
        .margin(10)
        .onClick(() => {
          this.selectedDate = new Date('2023-07-07');
        })
      DatePicker({
        start: new Date('1970-1-1'),
        end: new Date('2100-1-1'),
        selected: this.selectedDate
      })
      Child()
    }
  }
}
```

#### @Provide和@Consume支持联合类型实例

@Provide和@Consume支持联合类型和undefined和null。以下示例中，count类型为string | undefined，当点击祖先组件Ancestors中的Button改变count的属性或者类型时，Child中也会对应刷新。

```
@Component
struct Child {
  // @Consume装饰的变量通过相同的属性名绑定其祖先组件Ancestors内的@Provide装饰的变量
  @Consume count: string | undefined;

  build() {
    Column() {
      Text(`count(${this.count})`)
      Button(`count(${this.count}), Child`)
        .onClick(() => this.count = 'Ancestors')
    }
    .width('50%')
  }
}

@Component
struct Parent {
  build() {
    Row({ space: 5 }) {
      Child()
    }
  }
}

@Entry
@Component
struct Ancestors {
  // @Provide装饰的联合类型count由入口组件Ancestors提供其后代组件
  @Provide count: string | undefined = 'Child';

  build() {
    Column() {
      Button(`count(${this.count}), Child`)
        .onClick(() => this.count = undefined)
      Parent()
    }
  }
}
```

#### @Provide支持allowOverride参数

allowOverride：@Provide重写选项。

从API version 11开始使用。

| 名称 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| allowOverride | string | 否 | 是否允许@Provide重写。允许在同一组件树下通过allowOverride重写同名的@Provide。如果开发者未写allowOverride，定义同名的@Provide，运行时会报错。 |

```
@Component
struct MyComponent {
  // 使用allowOverride参数重写同名的@Provide
  @Provide({ allowOverride: 'reviewVotes' }) reviewVotes: number = 10;

  build() {
  }

}
```

完整示例如下：

```
@Component
struct GrandSon {
  // @Consume装饰的变量通过相同的属性名绑定其祖先内的@Provide装饰的变量
  @Consume('reviewVotes') reviewVotes: number;

  build() {
    Column() {
      Text(`reviewVotes(${this.reviewVotes})`) // Text显示10
      Button(`reviewVotes(${this.reviewVotes}), give +1`)
        .onClick(() => this.reviewVotes += 1)
    }
    .width('50%')
  }
}

@Component
struct Child {
  @Provide({ allowOverride: 'reviewVotes' }) reviewVotes: number = 10;

  build() {
    Row({ space: 5 }) {
      GrandSon()
    }
  }
}

@Component
struct Parent {
  @Provide({ allowOverride: 'reviewVotes' }) reviewVotes: number = 20;

  build() {
    Child()
  }
}

@Entry
@Component
struct GrandParent {
  @Provide('reviewVotes') reviewVotes: number = 40;

  build() {
    Column() {
      Button(`reviewVotes(${this.reviewVotes}), give +1`)
        .onClick(() => this.reviewVotes += 1)
      Parent()
    }
  }
}
```

在上面的示例中：

*   GrandParent声明了@Provide('reviewVotes') reviewVotes: number = 40。
*   Parent是GrandParent的子组件，声明@Provide为allowOverride，重写父组件GrandParent的@Provide('reviewVotes') reviewVotes: number = 40。如果不设置allowOverride，则会抛出运行时报错，提示@Provide重复定义。Child同理。
*   GrandSon在初始化@Consume的时候，@Consume装饰的变量通过相同的属性名绑定其最近的祖先的@Provide装饰的变量。
*   GrandSon查找到相同属性名的@Provide在祖先Child中，所以@Consume('reviewVotes') reviewVotes: number初始化数值为10。如果Child中没有定义与@Consume同名的@Provide，则继续向上寻找Parent中的同名@Provide值为20，以此类推。
*   如果查找到根节点还没有找到key对应的@Provide，则会报初始化@Consume找不到@Provide的报错。

#### @Consume装饰的变量支持设置默认值

从API version 20开始，@Consume装饰的变量支持设置默认值。

```
@Component
struct MyComponent {
  // @Consume装饰的变量设置默认值
  @Consume('withDefault') defaultValue: number = 10;

  build() {
  }

}
```

完整示例如下：

```
@Entry
@Component
struct Parent {
  @Provide('firstKey') provideOne: string | undefined = undefined;
  @Provide('secondKey') provideTwo: string = 'the second provider';

  build() {
    Column() {
      Row() {
        Column() {
          Text(`${this.provideOne}`)
          Text(`${this.provideTwo}`)
        }

        Column() {
          // 点击change provideOne按钮，provideOne和子组件中的textOne属性会同时变化
          Button('change provideOne')
            .onClick(() => {
              this.provideOne = undefined;
            })
          // 点击change provideTwo按钮，provideTwo和子组件中的textTwo属性会同时变化
          Button('change provideTwo')
            .onClick(() => {
              this.provideTwo = 'the next provider';
            })
        }
      }

      Row() {
        Column() {
          Child()
        }
      }
    }
  }
}

@Component
struct Child {
  // @Consume装饰的变量通过相同的别名绑定其祖先内的@Provide装饰的变量，同时设置默认值
  @Consume('firstKey') textOne: string | undefined = 'child';
  // @Consume装饰的变量通过相同的别名绑定其祖先内的@Provide装饰的变量，没有设置默认值
  @Consume('secondKey') textTwo: string;
  // @Consume装饰的变量在祖先内没有匹配成功的@Provide装饰的变量，但设置了默认值
  @Consume('thirdKey') textThree: string = 'defaultValue';

  build() {
    Column() {
      Text(`${this.textOne}`)
      Text(`${this.textTwo}`)
      Text(`${this.textThree}`)
      // 点击change textOne按钮，textOne和父组件的provideOne会同时变化
      Button('change textOne')
        .onClick(() => {
          this.textOne = 'not undefined';
        })
      // 点击change textTwo按钮，textTwo和父组件的provideTwo会同时变化
      Button('change textTwo')
        .onClick(() => {
          this.textTwo = 'change textTwo';
        })
    }
  }
}
```

在上面的示例中：

*   Parent声明了@Provide('firstKey') provideOne: string | undefined = undefined 与 @Provide('secondKey') provideTwo: string = 'the second provider'。
*   Child声明了@Consume('firstKey') textOne: string | undefined = 'child'，@Consume('secondKey') textTwo: string 与 @Consume('thirdKey') textThree: string = 'defaultValue'。
*   Child是Parent的子组件，Child在初始化@Consume装饰的三个属性时，textOne根据'firstKey'别名绑定Parent中的provideOne属性，provideOne的值会覆盖textOne的默认值，所以textOne初始化的值为undefined；textTwo根据'secondKey'别名绑定Parent中的provideTwo属性，textTwo初始化的值为'the second provider'；textThree在祖先组件中不存在匹配结果，如果@Consume没有设置默认值，则会抛出运行时错误，示例中textThree有默认值'defaultValue'，所以textThree初始化的值为'defaultValue'。
*   @Consume装饰的属性设置的默认值仅在祖先组件没有匹配结果时才生效，有匹配结果时无影响。

#### @Consume在跨BuilderNode场景下和@Provide建立双向同步

从API version 20开始，支持跨BuilderNode配对@Provide/@Consume。

BuilderNode支持@Provide/@Consume，需注意：

1.  在BuilderNode子树中定义的@Consume需要设置默认值，或者在子树中已存在配对的@Provide，否则会发生运行时报错。
2.  BuilderNode上树后，设置默认值的@Consume会向上查找@Provide，根据key的匹配规则找到最近的@Provide后，会和@Provide建立双向同步关系。如果找不到配对的@Provide，则@Consume仍使用默认值。
3.  建立双向同步的关系后，如果@Provide装饰变量的值和@Consume的默认值不同，则会回调@Consume的@Watch方法，以及与@Consume有同步关系的变量的@Watch方法，例如@Consume通知与其双向同步的@Link触发@Watch方法。
4.  BuilderNode下树后，@Consume会再次试图查找对应的@Provide，如果发现下树后无法再找到之前配对的@Provide，则断开和@Provide的双向同步关系，@Consume装饰的变量恢复成默认值。
5.  @Consume断开和@Provide的连接，恢复成默认值时，会判断@Consume装饰变量的值从和@Provide变为@Consume的默认值是否有变化，如果有变化，则会回调@Consume以及与其有同步关系变量的@Watch方法。

在下面的例子中：

1.  点击add Child:
    *   构建BuilderNode下的子节点Child，Child中@Consume未找到@Provide，使用本地默认值default value初始化。
    *   BuilderNode上树时，Child中@Consume向上找到最近的Index中的@Provide，将@Consume从默认值更新为@Provide的值，并回调@Consume的@Watch方法。
2.  @Provide和@Consume配对后，建立双向同步关系。点击Text(\`@Provide: ${this.message}\`)和Text(\`@Consume ${this.message}\`)，@Provide和@Consume绑定的Text组件刷新，并回调@Provide和@Consume的@Watch方法。
3.  点击remove Child, BuilderNode子节点下树，Child中的@Consume和Index中的@Provide断开连接，Child中的@Consume恢复成默认值，并回调@Consume的@Watch方法。
4.  点击dispose Child，释放BuilderNode下子节点，BuilderNode子节点Child销毁，执行aboutToDisappear。

```
import { NodeController, BuilderNode, FrameNode, UIContext } from '@kit.ArkUI';
import { hilog } from '@kit.PerformanceAnalysisKit';

const DOMAIN = 0x0000;

@Builder
function buildText() {
  Column() {
    Child()
  }
}

class TextNodeController extends NodeController {
  private rootNode: FrameNode | null = null;
  private uiContext: UIContext | null = null;
  private builderNode: BuilderNode<[]> | null = null;

  constructor() {
    super();
  }

  makeNode(context: UIContext): FrameNode | null {
    this.rootNode = new FrameNode(context);
    this.uiContext = context;
    // 将rootNode节点挂载在NodeContainer下
    return this.rootNode;
  }

  addBuilderNode(): void {
    if (this.builderNode === null && this.uiContext && this.rootNode) {
      this.builderNode = new BuilderNode(this.uiContext);
      // 配置跨BuilderNode支持@Provide/@Consume
      this.builderNode.build(wrapBuilder(buildText), undefined,
        { enableProvideConsumeCrossing: true });
      // 将BuilderNode的根节点挂载到rootNode节点下
      try {
        this.rootNode.appendChild(this.builderNode.getFrameNode());
      } catch (e) {
        hilog.error(DOMAIN, 'testTag', 'Failed to appendChild %{public}s', JSON.stringify(e) ?? '');
      }
    }
  }

  removeBuilderNode(): void {
    if (this.rootNode && this.builderNode) {
      // 从rootNode节点下的BuildNode节点移除
      try {
        this.rootNode.removeChild(this.builderNode.getFrameNode());
      } catch (e) {
        hilog.error(DOMAIN, 'testTag', 'Failed to removeChild %{public}s', JSON.stringify(e) ?? '');
      }
    }
  }

  disposeNode(): void {
    if (this.rootNode && this.builderNode) {
      // 立即释放当前BuilderNode
      this.builderNode.dispose();
    }
  }
}

@Entry
@Component
struct Index {
  @Provide @Watch('onChange') message: string = 'hello';
  controller: TextNodeController = new TextNodeController();

  onChange() {
    hilog.info(DOMAIN, 'testTag', '%{public}s', `Index Provide change ${this.message}`);
  }

  build() {
    Column() {
      Text(`@Provide: ${this.message}`)
        .fontSize(20)
        .onClick(() => {
          this.message += ' Provide';
        })

      // 执行BuilderNode的build方法，构造Child自定义组件
      // 并将BuilderNode挂载在NodeContainer下
      // Child中@Consume可以和当前Index中的@Provide配对
      // @Consume装饰的变量message从default value变为hello，并回调@Consume的@Watch方法
      Button('add Child')
        .onClick(() => {
          this.controller.addBuilderNode();
        })
      // 将BuilderNode下的节点从NodeContainer上移除
      // @Consume修饰的变量message从和@Provide配对的值变为default value，并回调@Consume的@Watch方法
      Button('remove Child')
        .onClick(() => {
          this.controller.removeBuilderNode();
        })

      // 立即释放当前BuilderNode，BuilderNode下节点销毁，Child组件执行aboutToDisappear
      Button('dispose Child')
        .onClick(() => {
          this.controller.disposeNode();
        })
      NodeContainer(this.controller)
        .width('100%')
        .height(100)
        .backgroundColor(Color.Pink)
    }
    .width('100%')
    .height('100%')
  }
}

@Component
struct Child {
  @Consume @Watch('onChange') message: string = 'default value';

  onChange() {
    hilog.info(DOMAIN, 'testTag', '%{public}s', `Child Consume change ${this.message}`);
  }

  aboutToDisappear(): void {
    hilog.info(DOMAIN, 'testTag', '%{public}s', `Child aboutToDisappear`);
  }

  build() {
    Column() {
      Text(`@Consume ${this.message}`)
        .fontSize(20)
        .onClick(() => {
          this.message += ' Consume';
        })
    }
  }
}
```

#### 常见问题

#### @BuilderParam尾随闭包情况下@Provide未定义错误

在此尾随闭包场景下，CustomWidget执行this.builder()创建子组件CustomWidgetChild时，this指向的是HomePage。因此找不到CustomWidget的@Provide变量，所以下面示例会报找不到@Provide错误，和@BuilderParam连用的时候要谨慎this的指向。

错误示例：

```
class Tmp {
  a: string = '';
}

@Entry
@Component
struct HomePage {
  // 错误点1：HomePage未声明@Provide
  @Builder
  builder2($$: Tmp) {
    Text(`${$$.a}测试`)
  }

  build() {
    Column() {
      // 错误点2：使用尾随闭包的形式将创建CustomWidgetChild的函数传递给CustomWidget，此时尾随闭包中this指向HomePage
      CustomWidget() {
        CustomWidgetChild({ builder: this.builder2 })
      }
    }
  }
}

@Component
struct CustomWidget {
  // 错误点3：@Provide变量声明在CustomWidget中，仅有CustomWidget自身及其子组件能够消费
  @Provide('a') a: string = 'abc';
  @BuilderParam
  builder: () => void;

  build() {
    Column() {
      Button('你好').onClick(() => {
        if (this.a == 'ddd') {
          this.a = 'abc';
        }
        else {
          this.a = 'ddd';
        }

      })
      this.builder()
    }
  }
}

@Component
struct CustomWidgetChild {
  // 错误点4：尝试消费CustomWidget的@Provide('a')，但实际上CustomWidgetChild的父组件为HomePage，无法找到对应的@Provide
  @Consume('a') a: string;
  @BuilderParam
  builder: ($$: Tmp) => void;

  build() {
    Column() {
      this.builder({ a: this.a })
    }
  }
}
```

正确示例：

```
class Tmp {
  public name: string = '';
}

@Entry
@Component
struct HomePage {
  // 修正点1：将@Provide声明在Entry组件（根作用域），确保子组件能正确消费
  @Provide('name') name: string = 'abc';

  @Builder
  builder2($$: Tmp) {
    Text(`${$$.name} test`)
  }

  build() {
    Column() {
      Button('Hello').onClick(() => {
        if (this.name == 'ddd') {
          this.name = 'abc';
        } else {
          this.name = 'ddd';
        }
      })
      // 修正点2：CustomWidget不再声明@Provide，仅作为容器传递builder
      CustomWidget() {
        CustomWidgetChild({ builder: this.builder2 })
      }
    }
  }
}

@Component
struct CustomWidget {
  @BuilderParam
  builder: () => void;

  build() {
    this.builder()
  }
}

@Component
struct CustomWidgetChild {
  // 修正点3：@Consume从根作用域（HomePage）获取@Provide('name')，作用域正确
  @Consume('name') name: string;
  @BuilderParam
  builder: ($$: Tmp) => void;

  build() {
    Column() {
      this.builder({ name: this.name })
    }
  }
}
```