鸿蒙 ArkUI 进阶:@Provide 和 @Consume,跨层传递的「直通车」,告别 props 层层透
�鸿蒙 ArkUI 进阶:@Provide 和 @Consume,跨层传递的「直通车」,告别 props 层层透
写在前面
如果你写 ArkUI 写过三层嵌套以上的组件,大概率遇到过这个场景:
根组件管「主题色」(蓝/红/绿)+「夜间模式开关」,主题色要传给最深的叶子按钮用。你一路 props 透传:根 → 中间层 → 叶子层,每层都接
theme参数再透给下一层。
写两个按钮还好,写到第十个你发现:中间层压根不用theme,只是为了透给叶子层才接这个参数——props 接了一堆自己不用的字段,组件签名臃肿到自己都不想看。
更头疼的是:主题色要再加个「次按钮色」,你得在每一层都加一个 props 字段,改了十层。
这是「跨层传递」的分水岭。鸿蒙 ArkUI 给的答案是 @Provide/@Consume——祖先装 @Provide 抛状态,后代装 @Consume 直接接,不用 props 层层透传。中间层完全不接这个参数,叶子层一样能拿到。
本文就用一个真机可跑的「三层嵌套主题色」demo,把 @Provide/@Consume 从「听名字一脸懵」讲到「下个项目直接抄」。代码托管在 AtomGit,文末有链接,真机实拍截图作证。
适合人群:写过 ArkUI、被 props 层层透折磨过的同学。
不适合人群:还在学@State/@Link的同学——出门左转看我的入门篇。
一、先讲清楚:@Provide 和 @Consume 到底是啥
一句话:@Provide 是祖先抛状态给后代,@Consume 是后代接祖先抛的状态,通过 aliasName 匹配。
你之前写 props 是「逐层透传」——每一层都接参数再传给下一层。@Provide/@Consume 是「直通车」——祖先抛一次,任意深的后代直接接,中间层完全不参与。
最小例子:
@Entry
@Component
struct Root {
// 祖先抛:aliasName = 'theme'
@Provide('theme') theme: ThemePref = new ThemePref()
build() {
Column() {
MiddleLayer() // 中间层不接 theme 参数
}
}
}
@Component
struct MiddleLayer {
build() {
Column() {
LeafLayer() // 中间层也不透给叶子
}
}
}
@Component
struct LeafLayer {
// 叶子接:aliasName = 'theme',自动找最近祖先 @Provide('theme')
@Consume('theme') theme: ThemePref = new ThemePref()
build() {
Text('当前主题色').fontColor(this.theme.primaryColor)
}
}
三个关键点:
@Provide('aliasName')抛状态:祖先装,aliasName 是匹配 key@Consume('aliasName')接状态:后代装,aliasName 要和祖先一致- 中间层完全不参与:不接参数不透传,叶子层照样能拿到
@Provide/@Consume 是双向同步
不是单向 props,是双向——后代改 @Consume 字段 = 祖先 @Provide 字段也改,反之亦然。这比 props 强大得多,props 是只读单向。
// 叶子层改 theme = 根层 theme 也改
this.theme.primaryColor = '#27AE60'
// 根层及其他所有 @Consume('theme') 的后代都同步
二、动手:一个「三层嵌套主题色」demo
demo 场景:根组件管主题色(蓝/红/绿)+ 夜间模式,三层深的叶子按钮直接用主题色——不用 props 透两层。
2.1 数据模型
class ThemePref {
primaryColor: string = '#007DFF'
darkMode: boolean = false
ThemePref() {}
}
用 class 不用 interface 字面量——ArkTS 强约束 arkts-no-untyped-obj-literals,裸对象字面量编译报错。这是新手第一坑。
2.2 根组件:@Provide 抛主题
@Entry
@Component
struct Index {
// @Provide:抛给后代,aliasName = 'theme'
// 自身已是状态装饰,不用再套 @State(套了报「不能多个状态装饰器」错)
@Provide('theme') theme: ThemePref = new ThemePref()
build() {
Column({ space: 14 }) {
Text('@Provide / @Consume 跨层传递 Demo').fontSize(22).fontWeight(FontWeight.Bold).margin({ top: 16 })
// ① 祶级控制面板:改 theme.primaryColor / darkMode
Column({ space: 10 }) {
Row({ space: 12 }) {
Text('主题色').fontSize(14).fontColor('#222')
Button('蓝').backgroundColor('#007DFF').fontColor('#fff').width(50)
.onClick(() => { this.theme.primaryColor = '#007DFF' })
Button('红').backgroundColor('#FF4D4F').fontColor('#fff').width(50)
.onClick(() => { this.theme.primaryColor = '#FF4D4F' })
Button('绿').backgroundColor('#27AE60').fontColor('#fff').width(50)
.onClick(() => { this.theme.primaryColor = '#27AE60' })
}
Row({ space: 12 }) {
Text('夜间模式').fontSize(14).fontColor('#222')
Toggle({ type: ToggleType.Switch, isOn: this.theme.darkMode })
.onChange((on: boolean) => { this.theme.darkMode = on })
}
}
.width('100%').padding(14).backgroundColor('#fff').borderRadius(10)
// ② 三层嵌套:中间层不透传 theme,叶子层用 @Consume 直接接
MiddleLayer()
}
.padding(16).backgroundColor(this.theme.darkMode ? '#1A1A1A' : '#F5F6F8').height('100%').width('100%')
}
}
2.3 中间层:完全不接 theme 参数
@Component
struct MiddleLayer {
build() {
Column({ space: 8 }) {
Text('中间层(不接 theme 参数)').fontSize(12).fontColor('#888')
LeafLayer() // 中间层也不透传 theme 给叶子
}
.width('100%').padding(10).backgroundColor('#FAFAFA').borderRadius(8)
}
}
中间层只透传叶子,不接 theme 参数也不透给叶子——这是 @Provide/@Consume 的核心价值,props 层层透就废了。
2.4 叶子层:@Consume 直接接
@Component
struct LeafLayer {
// @Consume 接 @Provide 抛的对象引用,双向同步
// aliasName 必须和祖先 @Provide 的 aliasName 一致
@Consume('theme') theme: ThemePref = new ThemePref()
build() {
Column({ space: 8 }) {
Text('叶子层(@Consume 直接接,不用 props)').fontSize(12).fontColor('#888')
// 用祖先抛的 theme.primaryColor / darkMode 直接渲染
Text('当前主题色').fontSize(14).fontColor(this.theme.primaryColor).fontWeight(FontWeight.Bold)
Row({ space: 8 }) {
Button('主按钮').backgroundColor(this.theme.primaryColor).fontColor('#fff').height(36)
Button('次按钮').backgroundColor(this.theme.darkMode ? '#444' : '#eee')
.fontColor(this.theme.darkMode ? '#fff' : '#333').height(36)
}
}
.width('100%').padding(10).backgroundColor(this.theme.darkMode ? '#2A2A2A' : '#fff').borderRadius(8)
}
}
叶子层用 @Consume('theme') 直接拿祖先抛的 theme,渲染时 this.theme.primaryColor/this.theme.darkMode 直接用——三层深一样能拿到,中间层完全不参与。
三、这段代码的三个关键点
① @Provide 自身已是状态装饰,不用套 @State
// ❌ 错:套 @State 报「不能多个状态装饰器」
@State @Provide('theme') theme: ThemePref = new ThemePref()
// ✅ 对:单独 @Provide,它自带状态管理
@Provide('theme') theme: ThemePref = new ThemePref()
@Provide 是「状态装饰器 + 抛给后代」二合一,不能再套 @State。新手第一坑。
② aliasName 必须祖先后代一致
@Provide('theme') theme: ThemePref // 祖先 aliasName = 'theme'
// ...
@Consume('theme') theme: ThemePref // 后代 aliasName 必须也是 'theme'
aliasName 是匹配 key,祖先后代必须字符串完全一致。不一致就接不上,后代字段保持默认值不报错但也不更新——这是新手第二坑(忘改 aliasName,以为装饰器没装上)。
③ 多祖先同名 @Provide,后代接最近的
@Provide('theme') rootTheme // 根抛
// ...
@Provide('theme') midTheme // 中间层也抛同名
// ...
@Consume('theme') theme // 叶子接:接中间层的 midTheme(最近祖先)
后代 @Consume 找最近祖先 @Provide,不跨过去找更远的。这是「就近原则」,类似 JS 作用域链。
四、真机实拍:三层嵌套跨层传递跑起来长这样
我把这个 demo 装到真机上跑(鸿蒙 6.1.1.125, API 24),下面这张是真机实拍,没有任何 P 图。
整体效果:两区块依次演示「① 祖宗改 theme 后代实时联」「② 三层嵌套叶子用 @Consume 直接接」:

重点看画面:第一区块是祖宗控制面板(蓝/红/绿三色按钮 + 夜间模式 Toggle);第二区块是三层嵌套(中间层标注「不接 theme 参数」+ 叶子层「当前主题色」蓝色文字 + 主按钮蓝色 + 次按钮灰色)——祖宗改主题色,叶子层实时联,中间层完全不接 theme 参数。这就是
@Provide/@Consume跨层传递的威力。
五、@Provide/@Consume vs props vs AppStorage:啥时候用哪个
新手最容易纠结的问题:既然有 props 和 AppStorage,还要 @Provide/@Consume 干啥?
| 机制 | 传递方式 | 双向同步 | 范围 | 何时用 |
|---|---|---|---|---|
props |
逐层透传 | 单向(只读) | 父→子 | 父直接给子的明确数据 |
@Provide/@Consume |
直通车 | 双向 | 祖先→任意深后代 | 跨多层传递,中间层不用 |
AppStorage+@StorageLink |
全局 key | 双向 | 应用全局 | 跨页面全局共享 |
一句话决策:父直接给子用 props,跨多层中间不用用 @Provide/@Consume,跨页面全局用 AppStorage。粒度选对,不要啥都往 AppStorage 塞。
六、常见坑(都是血泪)
| 坑 | 症状 | 解法 |
|---|---|---|
@State @Provide 套两个装饰器 |
编译报错「不能多个状态装饰器」 | @Provide 自带状态管理,单独装不套 @State |
@Consume 的 aliasName 写错 |
后代字段不更新保持默认值 | aliasName 必须和祖先 @Provide 字符串完全一致 |
@Provide/@Consume 装在非 class 字段 |
同步不稳 | 装 class 实例不要装裸值,ThemePref 显式声明 |
用 props 层层透替代 @Provide |
中间层 props 臂肿 | 跨多层中间不用就用 @Provide/@Consume,中间层不接参数 |
@Consume 期望跨页面 |
跨页面接不到 | 跨页面用 AppStorage+@StorageLink,@Consume 只本组件树内 |
多祖先同名 @Provide 期望接远的 |
接了最近的不是想要的 | 就近原则,要接远的改 aliasName 避冲突 |
七、完整代码仓库
本文所有代码都已托管到 AtomGit,欢迎 clone、提 issue、点 star:
🔗 仓库地址:https://atomgit.com/JaneConan/arkui-provide-consume
仓库包含:
- 完整的「三层嵌套主题色」跨层传递 demo 工程
Index.ets根组件(@Provide抛主题 + 控制面板)MiddleLayer中间层(完全不接 theme 参数,演示不用 props 透传)LeafLayer叶子层(@Consume直接接祖先抛的 theme)ThemePref数据模型 + ArkTS 装饰器正确用法示范- 可直接用 DevEco Studio 打开运行
八、下一步该学什么?
跑通这个 demo 之后,你的 ArkUI 跨层传递就齐了三件套:props(父→子)+ @Provide/@Consume(祖先→后代)+ AppStorage(全局)。建议按这个顺序往下:
@ObservedV2/@ComponentV2新装饰器体系:鸿蒙 6.1 新版状态管理,V2 比 V1 更精细@Computed计算属性:派生状态自动重算,比手写联动逻辑声明式@Watch+@Provide组合:祖先抛的状态变了自动跑逻辑,跨层响应- (转非 UI)HTTP 数据请求:
@ohos.net.http调 RESTful 接口,告别前端 fetch - (转非 UI)文件 IO:
@ohos.file.fs读写沙箱文件,大对象持久化正确姿势
写在最后
@Provide/@Consume 的本质,是**「跨层传递的直通车」**——祖先抛一次,任意深后代直接接,中间层完全不参与。这个思想在前端圈叫 React Context/Provide+Inject,在鸿蒙圈叫 @Provide/@Consume,名字不同灵魂相通。
一旦你开始用直通车思维写跨层传递,你会发现大部分「跨多层传数据」的需求,都是装饰器声明的自然结果。组件签名少一半臂肿字段,改动只改祖先一处,中间层干净如初。
代码已经给你了,仓库链接在上面。现在关掉这篇文章,打开 DevEco Studio,把 demo 跑起来,亲手改一个 @Consume 字段试试反向同步。
跑通了,回来评论区打个「1」,我看看有多少人真的动手了。🚀
作者:JaneConan
仓库:https://atomgit.com/JaneConan/arkui-provide-consume
协议:Apache-2.0,随便用,别告我
更多推荐




所有评论(0)