ArkTS 进阶之道(9):@Provide/@Consume 跨层传值——为啥不叫全局变量

ArkTS 进阶之道(9):@Provide/@Consume 跨层传值------为啥不叫全局变量

本文是「ArkTS 进阶之道」系列第 9 篇,续「ArkUI 状态哲学」阶段。上篇讲 @Prop/@Link 父子传值数据流绑定(篇 57)------父子两层数据流。本文讲跨层传值:@Provide/@Consume 跨层接 ------根因在键值注册机制,@Provide 注册键值供跨层 @Consume 接,不用逐层透传。能力系列篇 15 讲过 @Provide/@Consume 怎么用,本文讲为哈不叫全局变量------根因在键值注册不是全局作用域。

一、开篇:@Provide/@Consume 不是全局变量,是键值注册的跨层绑定

你写 TypeScript/React 时,跨层传值是「魔法」(React Context 顶层注册 + 孙层 useContext 接,中间层不透传):

typescript 复制代码
// React Context 跨层接
const SharedContext = createContext(0)
function Top() {
  const [value, setValue] = useState(0)
  return <SharedContext.Provider value={value}><Mid /></SharedContext.Provider>    ← 顶层注册
}
function Mid() { return <Grand /> }    ← 中间层不透传
function Grand() {
  const v = useContext(SharedContext)    ← 孙层跨层接顶层注册
  return <Text>{v}</Text>
}
// 孙层 useContext 跨层接顶层 Provider 注册值,中间层不透传

你写鸿蒙 ArkTS 时,@Provide/@Consume 贬饰器键值注册跨层接------不用手写 Context Provider:

typescript 复制代码
// ArkTS @Provide/@Consume 键值注册跨层接
@Entry
@Component
struct Top {
  @Provide('sharedKey') shared: number = 0    // 顶层 @Provide 注册键值
  build() {
    Column() { GrandLayer() }    // 中间层不透传
  }
}

@Component
struct GrandLayer {
  @Consume('sharedKey') shared: number    // 孙层 @Consume 跨层接键值
  build() { Text(`${this.shared}`) }
}
// 孙层 @Consume('sharedKey') 跨层接顶层 @Provide 注册的键值,中间层不透传

魔法 vs 注册的区别:React 把跨层传值当「魔法」(你手写 Context Provider 注册 + useContext 接),ArkTS 把 @Provide/@Consume 当「键值注册绑定」(装饰器编译期注册键值,跨层 @Consume 按键接)。根因不是魔法是注册------@Provide 编译期注册键值供跨层 @Consume 按键接,不是全局作用域的变量。

二、根因:@Provide/@Consume 的键值注册跨层绑定机制

鸿蒙 ArkUI 的 @Provide/@Consume 是键值注册跨层绑定------编译期 @Provide 注册键值,跨层 @Consume 按键接不用逐层透传,来自三重注册机制。

机制 1:@Provide 注册键值------编译期在组件树注册键值

@Provide 贬饰器编译期注册键值------在组件树顶层注册一个键值供跨层 @Consume 按键接:

typescript 复制代码
@Entry
@Component
struct Top {
  @Provide('sharedKey') shared: number = 0    // @Provide 注册键值 'sharedKey'
  build() {
    Column() {
      GrandLayer()    // 孙层跨层 @Consume('sharedKey') 接
    }
  }
}

注册键值 :@Provide shared 贬饰器编译期在组件树注册键值 'sharedKey'------不是全局作用域的变量,是组件树内按键查找的注册项。赋值 this.shared++ 触发通知所有 @Consume('sharedKey') 的组件重渲。注册键值不用手写 Context Provider,装饰器编译期注册。

机制 2:@Consume 按键跨层接------编译期按键查找上层注册

@Consume 贬饰器编译期按键跨层接------跨层向上查 @Provide 注册的同键值,不用逐层透传:

typescript 复制代码
@Component
struct MidLayer {
  build() {
    Column() { GrandLayer() }    // 中间层不透传 @Prop,直接嵌孙层
  }
}

@Component
struct GrandLayer {
  @Consume('sharedKey') shared: number    // @Consume 按键跨层接顶层 @Provide 注册
  build() {
    Text(`孙层 = ${this.shared}`)    ← 依赖 @Consume 跨层接的值
  }
}

按键跨层接 :@Consume shared 贬饰器编译期按键 'sharedKey' 向上查 @Provide 注册的同键值------跨过中间层 MidLayer 直接接顶层 Top 的 @Provide 注册值。不用中间层透传 @Prop,@Consume 按键跨层查找。根因不是全局变量是按键查找的注册绑定。

机制 3:@Provide/@Consume 追踪范围是组件树------跨层不逐层透传

@Provide/@Consume 编译期绑的追踪范围是组件树------@Provide 变通知所有 @Consume 同键组件重渲,跨层不逐层透传:

typescript 复制代码
// @Provide 追踪范围:组件树内所有 @Consume 同键组件
// 顶层 @Provide shared 变 → 通知所有 @Consume('sharedKey') 组件重渲(跨层)
// 中间层不用透传 @Prop,@Consume 按键直接跨层接

// 对比 @Prop 追踪范围:逐层透传
// 顶层 @State 变 → @Prop 传子 → 子 @Prop 传孙 → 孙 @Prop 刷(逐层透传链)
// 中间层要透传 @Prop,层数深时透传链冗长

追踪范围区别:@Provide/@Consume 编译期绑组件树范围------@Provide 变通知所有 @Consume 同键组件跨层重渲,不用逐层透传。对比 @Prop 逐层透传(父传子、子传孙、孙传孙孙),层数深时透传链冗长。根因不是全局变量是组件树范围的按键注册追踪。

三、真机配图:@Provide/@Consume 跨层接 vs @Prop 逐层透传对比证据

初始态(顶层 @Provide sharedValue=1、顶层 @State propSource=1、孙层 @Consume 接=1、孙层 @Prop 透传=1 均初始值):

点调改 @Provide + 改 @State 后(@Provide sharedValue=2 跨层 @Consume 孙刷不透传、@State propSource=2 逐层 @Prop 透传刷均对比证据齐):

对比证据:改 @Provide sharedValue 后跨层 @Consume 孙层直接刷(中间层不透传),改 @State propSource 后 @Prop 逐层透传刷(中间层要透传)。@Provide/@Consume 不是全局变量是键值注册的跨层绑定------@Provide 注册键值供跨层 @Consume 按键接,不用逐层透传,根因在按键查找的注册绑定不是全局作用域。

四、真解法:@Provide/@Consume 用法的三个场景

场景 1:跨层共享状态(孙层接顶层,不逐层透传,深嵌套首选)

typescript 复制代码
@Entry
@Component
struct Top {
  @Provide('theme') theme: string = 'light'    // @Provide 注册主题键值
  @Provide('lang') lang: string = 'zh'         // @Provide 注册语言键值
  build() {
    Column() { MidLayer() }    // 中间层不透传,孙层 @Consume 跨层接
  }
}

@Component
struct MidLayer {
  build() { Column() { GrandLayer() } }    // 中间层不透传 @Prop
}

@Component
struct GrandLayer {
  @Consume('theme') theme: string    // 孙层 @Consume 跨层接顶层主题
  @Consume('lang') lang: string       // 孙层 @Consume 跨层接顶层语言
  build() {
    Column() {
      Text(`主题:${this.theme}`)
      Text(`语言:${this.lang}`)
    }
  }
}

为哈能跑 :@Provide/@Consume 跨层共享------孙层 @Consume �跨层接顶层 @Provide 注册值,中间层不透传。深嵌套首选这个,层数深时 @Prop 逐层透传链冗长,@Provide/@Consume �跨层接清爽。要写「顶层管全局配置 + 孙层跨层接」时用这个------不用逐层透传 @Prop,@Consume 按键跨层接。

场景 2:多孙层同键接(多个孙层接同个 @Provide,一键多接)

typescript 复制代码
@Entry
@Component
struct Top {
  @Provide('user') userName: string = '游客'    // @Provide 注册用户键值
  build() {
    Column() {
      HeaderLayer()    // 多个孙层都 @Consume('user') 接同键
      ContentLayer()
      FooterLayer()
    }
  }
}

@Component
struct HeaderLayer {
  @Consume('user') userName: string    // 孙层 1 @Consume 接用户键值
  build() { Text(`头部:${this.userName}`) }
}

@Component
struct ContentLayer {
  @Consume('user') userName: string    // 孙层 2 @Consume 接同键值
  build() { Text(`内容:${this.userName}`) }
}

@Component
struct FooterLayer {
  @Consume('user') userName: string    // 孙层 3 @Consume 接同键值
  build() { Text(`底部:${this.userName}`) }
}

为哈能跑 :@Provide 注册一个键值,多个 @Consume 同键组件都跨层接------顶层 @Provide userName 变通知所有 @Consume('user') 的孙层重渲。要写「顶层管用户状态 + 多个孙层都接」时用这个------一键多接,@Provide 变所有 @Consume 同键组件刷,不用逐个 @Prop 透传。

typescript 复制代码
// 跨层深(3 层以上)用 @Provide/@Consume:不逐层透传
@Entry
@Component
struct Top {
  @Provide('data') data: number = 0    // 跨层 3 层以上用 @Provide
  build() { Column() { Mid1() } }
}
struct Mid1 { build() { Mid2() } }     // 中间层不透传
struct Mid2 { build() { GrandLayer() } }
struct GrandLayer {
  @Consume('data') data: number    // 孙层跨层接,不透传
  build() { Text(`${this.data}`) }
}

// 父子近(2 层)用 @Prop/@Link:直接父子传
@Entry
@Component
struct Parent {
  @State value: number = 0          // 父子 2 层用 @Prop/@Link
  build() { Column() { Child({ v: this.value }) } }
}
struct Child {
  @Prop v: number                    // 父子近用 @Prop 直接传
  build() { Text(`${this.v}`) }
}

为哈能跑:@Provide/@Consume vs @Prop/@Link 选型按嵌套深度------跨层深(3 层以上)用 @Provide/@Consume 不逐层透传,父子近(2 层)用 @Prop/@Link 直接传。要写「嵌套深的组件树共享状态」时用 @Provide/@Consume,要写「父子两层直接传」时用 @Prop/@Link------按嵌套深度选型,跨层深透传链冗长用注册,父子近直接传用绑定。

五、一句话哲学

@Provide/@Consume 不是全局变量,是键值注册的跨层绑定。 ArkUI 的 @Provide 贬饰器编译期在组件树注册键值供跨层 @Consume 按键接,@Consume 贬饰器编译期按键向上查 @Provide 注册的同键值跨层接不用逐层透传。根因不是全局作用域的变量是按键查找的注册绑定------@Provide 注册键值(组件树注册)+ @Consume 按键跨层接(向上查同键)+ 追踪范围组件树(跨层不逐层透传)。对比 @Prop 逐层透传链冗长,@Provide/@Consume 跨层接清爽。

状态哲学阶段串讲:@State 单组件内部(篇 56,赋值就刷 UI 依赖追踪)→ @Prop/@Link 父子传值(篇 57,单向 vs 双向数据流绑定)→ @Provide/@Consume 跨层传值(篇 58,键值注册跨层绑定)------从「单组件内部」到「父子两层」到「跨层多层」扩展,根因都是编译期绑追踪范围。

为啥不叫全局变量:全局变量是全局作用域任意处可访问,@Provide/@Consume 是组件树内按键注册查找------作用域不是全局是组件树,查找不是任意是按键,绑定不是作用域是注册。叫键值注册的跨层绑定不是全局变量,根因在按键查找的注册绑定机制。

下一篇:ArkTS 进阶之道(10)------ @Watch 状态监听:从回调时机理解响应式边界(对应能力系列篇 16,讲根因)------续「ArkUI 状态哲学」阶段收尾。

能力系列回链

能力系列篇 本文进阶点
篇 15 @Provide/@Consume 用法 键值注册跨层绑定根因
篇 14 @Prop/@Link 用法 上一篇:单向 vs 双向数据流绑定
篇 16 @Watch 用法 下篇预告:状态监听回调时机根因

真机 demo 完整代码

typescript 复制代码
// 篇 58 demo:@Provide/@Consume 跨层传值 vs @Prop 逐层透传对比
// 用 V1 贬饰器(@Component 里用,避 V2 双轨坑)

// 中间层组件:@Prop 逐层透传(对比证据)
@Component
struct MidPropLayer {
  @Prop passed: number          // @Prop 逐层透传:父传中、中传孙
  build() {
    Column({ space: 6 }) {
      Text(`中间层 @Prop = ${this.passed}`).fontSize(13).fontColor('#888')
      // 孙层接 @Prop 透传值
      Text(`孙层接透传 = ${this.passed}`).fontSize(14).fontColor('#2563eb')
    }
    .alignItems(HorizontalAlign.Center)
  }
}

// 中间层组件:@Consume 直接接(不透传)
@Component
struct MidConsumeLayer {
  @Consume('sharedKey') shared: number    // @Consume 直接接上层 @Provide,不透传
  build() {
    Column({ space: 6 }) {
      Text(`中间层 @Consume = ${this.shared}`).fontSize(13).fontColor('#dc2626')
      // 孙层也 @Consume 直接接(不透传)
      GrandConsumeLayer()
    }
    .alignItems(HorizontalAlign.Center)
  }
}

// 孙层组件:@Consume 直接接(跨层不透传)
@Component
struct GrandConsumeLayer {
  @Consume('sharedKey') shared: number    // 孙层 @Consume 跨层直接接顶层 @Provide
  build() {
    Text(`孙层 @Consume 跨层接 = ${this.shared}`).fontSize(14).fontColor('#dc2626')
  }
}

@Entry
@Component
struct Index {
  // ✅ @Provide 贬饰器:跨层提供,不逐层透传
  @Provide('sharedKey') sharedValue: number = 1
  // @Prop 透传用的源
  @State propSource: number = 1
  @State log: string = '(未操作)'

  build() {
    Column({ space: 12 }) {
      Text('篇 58 配图:@Provide/@Consume 跨层传值 vs @Prop 逐层透传')
        .fontSize(18).fontWeight(FontWeight.Bold).margin({ top: 20, bottom: 8 })
      Text('@Consume 跨层直接接 vs @Prop 逐层透传(对比证据)')
        .fontSize(12).fontColor('#888').margin({ bottom: 16 })

      Column({ space: 6 }) {
        Text(`顶层 @Provide sharedValue = ${this.sharedValue}`).fontSize(15).fontWeight(FontWeight.Bold)
        Text(`顶层 @State propSource = ${this.propSource}`).fontSize(15).fontWeight(FontWeight.Bold)
        Text(`日志:${this.log}`).fontSize(12).fontColor('#333').margin({ top: 4 })
      }
      .width('92%').padding(12).backgroundColor('#f5f5f5').borderRadius(8)

      // @Prop 逐层透传:顶层 → 中间层 → 孙层(要逐层传)
      Text('@Prop 逐层透传链路:').fontSize(13).fontColor('#2563eb')
      MidPropLayer({ passed: this.propSource })

      // @Provide/@Consume 跨层接:顶层 Provide → 孙层 Consume(不透传)
      Text('@Provide/@Consume 跨层接链路:').fontSize(13).fontColor('#dc2626')
      MidConsumeLayer()

      Button('改 @Provide sharedValue(跨层孙刷)')
        .width('92%').height(44).fontSize(14)
        .onClick(() => {
          this.sharedValue++              // 改 @Provide,跨层 @Consume 孙刷(不透传)
          this.log = `改 sharedValue=${this.sharedValue}(跨层 @Consume 孙刷,不透传)`
        })

      Button('改 @State propSource(逐层透传刷)')
        .width('92%').height(44).fontSize(14)
        .onClick(() => {
          this.propSource++              // 改 @State,@Prop 逐层透传刷
          this.log = `改 propSource=${this.propSource}(@Prop 逐层透传刷)`
        })
    }
    .width('100%').height('100%').alignItems(HorizontalAlign.Center)
  }
}

写鸿蒙 ArkUI 记住 :@Provide/@Consume 不是全局变量是键值注册的跨层绑定------@Provide 贬饰器编译期在组件树注册键值供跨层 @Consume 按键接,@Consume 贬饰器编译期按键向上查 @Provide 注册的同键值跨层接不用逐层透传。根因不是全局作用域的变量是按键查找的注册绑定------作用域是组件树不是全局,查找是按键不是任意,绑定是注册不是作用域。跨层深(3 层以上)用 @Provide/@Consume 不逐层透传(深嵌套首选),父子近(2 层)用 @Prop/@Link 直接传,一键多接用 @Provide 多孙层 @Consume 同键。按键注册跨层接不逐层透传是 ArkUI 状态哲学核心!

相关推荐
AskHarries1 小时前
错误监控怎么做
后端
MacroZheng1 小时前
同事:“Claude Code都能自动写代码了,还要什么Spec Coding?” 我反问:“屎山代码你来维护?”
java·人工智能·后端
爱勇宝1 小时前
《道德经》第 8 章:真正高级的能力,像水一样成事
前端·后端·程序员
Zane19941 小时前
可变对象 vs 不可变对象:为什么"可变默认参数"是 Python 最经典的坑?
后端·python
未秃头的程序猿2 小时前
JDK 26的Value Class,我做了个性能测试,结果出乎意料
java·后端·面试
swipe2 小时前
10|(前端转全栈)库存扣减为什么最容易出事故?SKU、并发与原子更新
前端·后端·全栈
smallYoung2 小时前
学习笔记-python基础(day12 正则表达式)
后端
Zane19942 小时前
volatile / synchronized / final:三大特性(原子性/可见性/有序性)到底谁保证了什么?
java·后端
网易云信2 小时前
销售为什么是企业 AI 落地的"最佳突破口"?
人工智能·后端·agent