在 ArkUI 里,除了 @State@Prop 这些状态/属性装饰器之外,还有一个很偏底层、但非常好用的能力:@Env 环境变量装饰器。

它的作用可以简单理解为:

把系统/运行环境的一些“全局状态”,以属性的形式注入到组件里,让 UI 能“感知环境变化”。

这篇文章就带你从 0 上手 @Env,并给出一个可直接改造进项目的示例。


一、@Env 是什么?能做什么?

官方定义:

  • 模块从 API Version 22 开始支持;
  • 支持元服务(Meta Service)使用;
  • 需要系统能力:SystemCapability.ArkUI.ArkUI.Full
  • 核心能力:提供 Env 这个装饰器,用来把系统环境变量注入 ArkUI 组件字段

基础用法长这样:

import { uiObserver } from '@kit.ArkUI';

@Entry
@Component
struct Index {
  @Env(SystemProperties.BREAK_POINT)
  breakpoint: uiObserver.WindowSizeLayoutBreakpointInfo;

  build() {
    // 根据 breakpoint 做自适应布局
  }
}

这里有三件事:

  1. 使用 @Env(...) 装饰组件字段;
  2. 参数是一个 SystemProperties 枚举值(环境变量的“key”);
  3. 装饰后的字段类型由这个环境变量决定,比如 BREAK_POINT 对应 WindowSizeLayoutBreakpointInfo
✅ 重点:当 @Env 写在 @Component / @ComponentV2 内部字段上时,它能拿到当前窗口的一些环境信息,而不是全局单例。

二、核心类型:EnvDecorator & SystemProperties

2.1 EnvDecorator 类型定义

declare type EnvDecorator = (value: SystemProperties) => PropertyDecorator;

也就是说:

  • Env 自己就是一个函数;
  • 它接受一个枚举值 SystemProperties
  • 返回一个 PropertyDecorator,用于修饰组件字段。

你平时用到的就是这个形式:

@Env(SystemProperties.BREAK_POINT)
breakpoint: uiObserver.WindowSizeLayoutBreakpointInfo;

2.2 SystemProperties 枚举

当前文档里只暴露了一个枚举值:

enum SystemProperties {
  BREAK_POINT = 'system.arkui.breakpoint'
}

说明:

  • BREAK_POINT:通过 @Env(SystemProperties.BREAK_POINT) 能获取到一个
    uiObserver.WindowSizeLayoutBreakpointInfo 实例;
  • 当装饰器声明在 @Component / @ComponentV2 里时,用来获取当前自定义组件所在窗口尺寸布局断点信息

简单理解:

这个 breakpoint 可以用来做「手机/平板/大屏」之类的响应式 UI 控制逻辑。

三、错误码:140000 如何排查?

@Env 只有一个官方错误码,非常好记:

错误码 ID错误信息含义
140000Invalid key for @Env传给 @Env(...) 的 key 不合法(不是支持的 SystemProperties

常见触发方式:

// ❌ 错误示例:写了不存在的 key
@Env('system.arkui.xx' as any)
env: any;

排查建议:

  1. 一定要使用 SystemProperties 枚举,不要手写字符串:

    @Env(SystemProperties.BREAK_POINT)
    breakpoint: uiObserver.WindowSizeLayoutBreakpointInfo;
  2. 确认当前 SDK / API Level 是否已经 ≥ 22
  3. 检查是不是写错了导入,或自定义了同名枚举覆盖了系统的 SystemProperties

四、最小可运行示例:打印窗口断点信息

先来一个最简单的 Demo:把断点信息打印出来,方便你在真机/模拟器上看效果。

import { uiObserver } from '@kit.ArkUI';

@Entry
@Component
struct BreakpointDemo {
  @Env(SystemProperties.BREAK_POINT)
  breakpoint: uiObserver.WindowSizeLayoutBreakpointInfo;

  build() {
    Column() {
      Text('当前窗口断点信息:')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 8 })

      // 简单直接:把对象序列化出来看
      Text(JSON.stringify(this.breakpoint))
        .fontSize(14)
        .fontColor('#999999')
        .lineHeight(18)
        .textAlign(TextAlign.Start)
        .margin({ left: 12, right: 12 })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

建议你:

  • 在手机、平板、大屏或者调整窗口大小时多试试;
  • 观察 JSON.stringify(this.breakpoint) 输出的字段结构;
  • 再根据实际字段来写你的业务判断(比如宽度区间、layout 类型等)。
⚠️ 注意:WindowSizeLayoutBreakpointInfo 的字段以当前 SDK 官方文档为准,这里用 JSON.stringify 的方式,就是为了避免你一开始就被字段名卡住。

五、实战:用 @Env 写一个响应式布局

下面是一个「手机一列、大屏两列」的简化示例。重点是思路,你可以根据实际字段名调整判断逻辑。

5.1 思路设计

  1. @Env(SystemProperties.BREAK_POINT) 拿到断点信息;
  2. 根据断点信息判断当前属于 COMPACT / MEDIUM / EXPANDED 之类的类别(具体枚举以 SDK 为准);
  3. 用一个 getter 或方法,将断点映射到“列数”、“间距”等 UI 参数;
  4. build() 里根据这些参数布局内容。

5.2 示例代码(判断逻辑示意)

import { uiObserver } from '@kit.ArkUI';

@Entry
@Component
struct ResponsiveGridPage {
  @Env(SystemProperties.BREAK_POINT)
  breakpoint: uiObserver.WindowSizeLayoutBreakpointInfo;

  // 根据断点信息,推导当前列数(伪代码,具体判断按实际字段改)
  private get columnCount(): number {
    // 根据实际字段来写,比如 this.breakpoint.windowSizeClass / width / type 等等
    // 这里用伪逻辑举例:
    // - 小屏:1 列
    // - 中屏及以上:2 列
    // 请结合自己工程中的 WindowSizeLayoutBreakpointInfo 实际字段来判断
    try {
      // 你可以先打印 breakpoint 再决定判断方式
      return  this.isLargeLike() ? 2 : 1;
    } catch (e) {
      // 容错:拿不到断点时,降级为 1 列
      return 1;
    }
  }

  private isLargeLike(): boolean {
    // 这里仅示意:真实项目里用宽度、sizeClass 等字段来判断
    // 比如:
    // return this.breakpoint.width >= 600;
    console.info('breakpoint:', JSON.stringify(this.breakpoint));
    return false;
  }

  build() {
    Column() {
      Text('响应式布局示例(基于 @Env 断点)')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 12 })

      // 简单模拟一个“宫格列表”
      this.buildGrid()
    }
    .width('100%')
    .height('100%')
    .padding(16)
  }

  private buildGrid() {
    // 为了示例简单,这里模拟 6 个 Item
    const items: number[] = [1, 2, 3, 4, 5, 6];

    if (this.columnCount === 1) {
      // 一列:竖向列表
      Column({ space: 8 }) {
        ForEach(items, (item: number) => {
          this.buildCard(item)
        })
      }
    } else {
      // 两列:简单两列栅格(更复杂的可以用自定义布局组件)
      Column({ space: 8 }) {
        ForEach(this.splitToRows(items, 2), (row: number[], index: number) => {
          Row({ space: 8 }) {
            ForEach(row, (item: number) => {
              // 每列占据一半空间
              this.buildCard(item)
                .layoutWeight(1)
            })
          }
        })
      }
    }
  }

  // 工具:把一维数组拆成二维
  private splitToRows(list: number[], count: number): number[][] {
    const result: number[][] = [];
    let temp: number[] = [];
    list.forEach((v, i) => {
      temp.push(v);
      if (temp.length === count || i === list.length - 1) {
        result.push(temp);
        temp = [];
      }
    });
    return result;
  }

  private buildCard(index: number) {
    return Column() {
      Text(`Card ${index}`)
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
      Text('这里是内容区域,可以放图片、标题、按钮等。')
        .fontSize(12)
        .fontColor('#999999')
        .margin({ top: 4 })
    }
    .padding(12)
    .backgroundColor('#FFFFFF')
    .borderRadius(12)
    .shadow({ radius: 8, color: '#22000000', offsetY: 2 })
  }
}

上面例子里,有几点可以参考到自己的项目里:

  • @Env(...) 注入的环境变量封装成 getter/方法;
  • 组件内部只关心“几列”“间距多大”,而不关心“断点枚举”细节;
  • 后续要改断点规则,只用改 columnCount 的计算逻辑。

六、@Env 使用注意事项

6.1 只能装饰属性,且用在组件里才有意义

  • @Env 是装饰字段的,不是方法;
  • 建议用在 @Component / @ComponentV2 内部;
  • 如果你在普通类里用,通常是拿不到期望的 UI 环境(即使类型上不报错)。

6.2 环境变量是“只读语义”

虽然语法上你可以给字段重新赋值,但语义上 @Env 注入的是环境变量

  • 把它当“只读快照 + 自动更新”的数据源;
  • 不要指望在组件里 this.breakpoint = xxx 去修改系统状态。

6.3 响应性 & 性能

通常来说,@Env 注入的变量会随环境变化(比如窗口尺寸变更)而更新,你可以:

  • 直接在 build() 或 getter 里使用;
  • 如果需要更精细控制,可以配合自定义逻辑,在 aboutToAppear 中打印一次,了解变化频率,再做优化。

七、什么时候应该用 @Env?

可以简单记一个心法:

当你写 UI 时,发现需要「感知设备 /窗口环境」时,就可以想一想:能不能用 @Env?

典型场景包括:

  1. 响应式布局:

    • 不同断点展示不同列数、不同导航结构;
    • 小屏用 Tab,大屏用侧栏 + 内容区域。
  2. 窗口多实例 / 多窗口:

    • 同一个组件被复用到不同窗口中,需要根据各自窗口环境分别调整。
  3. 元服务 / 卡片场景:

    • 某些运行形态下环境信息不同,通过 @Env 拿到差异,裁剪 UI。

八、总结

@Env 看起来只是一个小小的装饰器,但定位其实很清晰:

  • @State / @Prop 管组件内部/外部数据;
  • @Env 管组件所处的“环境维度”的信息。

掌握它之后,你可以把「环境感知」这件事,从零散的 getWindowRect、全局单例逻辑中抽离出来,用更声明式、更 ArkUI 风格的写法来组织代码。

标签: ArkUI, @Env, SystemProperties, BREAK_POINT, uiObserver, WindowSizeLayoutBreakpointInfo, API Version 22, Meta Service, EnvDecorator, @Component

添加新评论