鸿蒙原生开发手记:徒步迹 - @Provide/@Consume 跨组件通信

掌握跨级组件通信,构建灵活可复用的组件体系


一、前言

在复杂应用中,经常出现祖先组件需要向后代组件传递数据的场景。如果逐层通过 @Prop 传递,会导致"属性逐层传递"的问题(prop drilling),代码冗长且难以维护。

ArkUI 提供了 @Provide/@Consume 装饰器对,实现跨级组件通信,无需中间组件参与。


二、@Provide/@Consume 原理

2.1 基本概念

@Provide 祖先组件
   │
   │ (自动传递)
   │
   v
@Consume 后代组件 1    @Consume 后代组件 2
  • @Provide:在祖先组件中定义,提供数据
  • @Consume:在后代组件中声明,自动获取祖先的 Provide 数据

2.2 基础用法

// 祖先组件 - 提供数据
@Component
struct AppRoot {
  @Provide('isLoggedIn') isLoggedIn: boolean = false;
  @Provide('userName') userName: string = '';

  build() {
    Column() {
      // 中间组件(不需要传递任何数据)
      MainContent();
    }
  }
}

// 中间组件 - 不接触数据
@Component
struct MainContent {
  build() {
    Column() {
      UserProfile();
    }
  }
}

// 后代组件 - 消费数据
@Component
struct UserProfile {
  @Consume('isLoggedIn') isLoggedIn: boolean;
  @Consume('userName') userName: string;

  build() {
    Column() {
      if (this.isLoggedIn) {
        Text(`欢迎, ${this.userName}`)
          .fontSize(18);
      } else {
        Button('登录');
      }
    }
  }
}

2.3 类型推断(省略别名)

当不指定别名时,@Provide/@Consume 使用变量名自动匹配:

@Component
struct Ancestor {
  @Provide stepCount: number = 0;   // 变量名作为标识

  build() {
    Descendant();
  }
}

@Component
struct Descendant {
  @Consume stepCount: number;       // 自动匹配同名变量
  // 等价于 @Consume('stepCount') stepCount: number;

  build() {
    Text(`步数: ${this.stepCount}`);
  }
}

三、@Provide/@Consume vs @Prop/@Link

特性 @Prop/@Link @Provide/@Consume
传递层级 父子之间 任意层级
中间组件 需要逐层传递 无需参与
使用场景 直接父子 跨级祖先→后代
性能 较好 稍差(需查找)
可读性 显式传递 隐式传递

选择建议

  • 父子组件:用 @State/@Prop/@Link
  • 跨 2 层以上:用 @Provide/@Consume
  • 全局状态:用 AppStorage

四、实战:徒步迹全局登录状态

在徒步迹 App 中,登录状态需要在多个页面间共享:

// AppRoot - 应用根组件
@Entry
@Component
struct AppRoot {
  @Provide('isLogged') isLogged: boolean = false;
  @Provide('currentUser') currentUser: User | null = null;
  @Provide('themeMode') themeMode: string = 'light';

  build() {
    Column() {
      if (!this.isLogged) {
        LoginPage();
      } else {
        MainTabPage();
      }
    }
    .width('100%')
    .height('100%');
  }
}

4.1 登录页面使用

@Component
struct LoginPage {
  @Consume('isLogged') isLogged: boolean;
  @Consume('currentUser') currentUser: User | null;

  build() {
    Column() {
      Button('登录')
        .onClick(() => {
          // 成功后更新全局状态
          this.isLogged = true;
          this.currentUser = {
            id: 1,
            nickname: '徒步爱好者',
            avatar: '',
            phone: '138****0000',
          };
        });
    }
  }
}

4.2 个人中心页面使用

@Component
struct ProfilePage {
  @Consume('isLogged') isLogged: boolean;
  @Consume('currentUser') currentUser: User | null;

  build() {
    Column() {
      if (this.currentUser) {
        Text(this.currentUser.nickname)
          .fontSize(20)
          .fontWeight(FontWeight.Bold);

        Text(`手机: ${this.currentUser.phone}`)
          .fontSize(14)
          .fontColor('#666');
      }

      Button('退出登录')
        .onClick(() => {
          this.isLogged = false;
          this.currentUser = null;
        });
    }
  }
}

五、多个 Provide/Consume 的协作

// 追踪页 - 提供多个状态
@Component
struct TrackingSection {
  @Provide('status') status: string = 'idle';
  @Provide('trackData') trackData: TrackData = {
    duration: 0,
    distance: 0,
    points: [],
  };

  build() {
    Column() {
      ControlPanel();
      StatsDisplay();
      MapView();
    }
  }
}

// 控制面板 - 修改状态
@Component
struct ControlPanel {
  @Consume('status') status: string;
  @Consume('trackData') trackData: TrackData;

  build() {
    Button('开始')
      .onClick(() => {
        this.status = 'recording';
      });
  }
}

// 统计显示 - 读取状态
@Component
struct StatsDisplay {
  @Consume('trackData') trackData: TrackData;

  build() {
    Text(`距离: ${trackData.distance.toFixed(2)}km`);
  }
}

六、AppStorage - 应用级全局存储

除了 @Provide/@Consume,ArkUI 还提供了 AppStorage 用于应用级状态管理:

// 1. 在 Ability 中初始化
export default class EntryAbility extends UIAbility {
  onCreate(): void {
    AppStorage.setOrCreate('token', '');
    AppStorage.setOrCreate('userId', 0);
    AppStorage.setOrCreate('stepGoal', 10000);
  }
}

// 2. 在组件中使用
@Entry
@Component
struct HomePage {
  @StorageLink('token') token: string = '';
  @StorageProp('stepGoal') stepGoal: number = 10000;

  build() {
    Text(`目标步数: ${this.stepGoal}`);
  }
}
API 说明
@StorageLink(key) 双向绑定,组件修改同步到 AppStorage
@StorageProp(key) 单向绑定,组件只读
AppStorage.set() 设置值
AppStorage.get<T>(key) 获取值
AppStorage.setOrCreate(key) 不存在时创建

七、LocalStorage - 页面级共享

LocalStorage 是 Ability 或页面级别的共享存储:

// 创建页面级存储
let localStorage = new LocalStorage({
  'pageTitle': '徒步迹',
  'pageType': 'home',
});

// 在页面中使用
@Entry(localStorage)
@Component
struct HomePage {
  @LocalStorageLink('pageTitle') title: string = '';

  build() {
    Text(this.title);
  }
}

八、状态管理选择指南

应用级全局数据?
    ├── 是 → AppStorage(token、用户信息、设置)
    └── 否 → 组件间层级?
              ├── 跨多级 → @Provide/@Consume(页面级共享状态)
              ├── 父子 → @Prop/@Link
              └── 本组件 → @State

九、总结

@Provide/@Consume 是解决跨级组件通信的利器,配合 AppStorage 和 LocalStorage,可以应对各种状态管理场景。在徒步迹 App 中,我们使用 @Provide/@Consume 管理登录状态和主题模式,后续还会用于轨迹记录的实时数据共享。

下一篇文章将学习 @Watch 和 @ObjectLink 装饰器,实现更精细的状态监听。


下一篇预告:鸿蒙原生开发手记:徒步迹 - @Watch 与 @ObjectLink 深入理解

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐