@Environment的那些事

3 阅读2分钟

先理解 @Environment 和 @EnvironmentObject 的区别

它们名字很像,但用途不同:

@EnvironmentObject@Environment
注入什么你自定义的 ObservableObjectSwiftUI/系统预设的环境值
key 是什么类型本身KeyPath(.colorScheme, .locale 等)
谁来注入你自己调用 .environmentObject()系统自动注入,也可以覆盖
典型场景业务数据(登录用户、购物车)外观设置(深色模式、字体大小、语言)

读取系统环境值

SwiftUI 内置了大量环境值,通过 @Environment 可以直接读取:

struct ContentView: View {
   // 读取系统的深色/浅色模式
   @Environment(\.colorScheme) var colorScheme

   // 读取系统语言/地区
   @Environment(\.locale) var locale

   // 读取无障碍:用户是否开启了"减少动态效果"
   @Environment(\.accessibilityReduceMotion) var reduceMotion

   // 读取当前字体大小级别(用户在系统设置里调的)
   @Environment(\.dynamicTypeSize) var typeSize

   var body: some View {
       Text("你好")
           .foregroundColor(colorScheme == .dark ? .white : .black)
   }
}

不需要任何注入,系统自动把这些值放进环境里,你直接取就行。


覆盖环境值(为子树设置特定环境)

.environment() 可以覆盖子树的环境值,常用于 Preview 或局部样式控制:

struct ContentView: View {
   var body: some View {
       VStack {
           NormalView()

           // 给这个子树强制设置深色模式
           DarkPreviewArea()
               .environment(\.colorScheme, .dark)

           // 给这个子树强制设置某种语言
           LocalizedSection()
               .environment(\.locale, Locale(identifier: "ja"))
       }
   }
}

自定义 EnvironmentKey(高级用法)

你也可以自己往 SwiftUI 的环境系统里注册新的 key:

// 第一步:定义一个 EnvironmentKey,声明类型和默认值
struct ThemeColorKey: EnvironmentKey {
   static let defaultValue: Color = .blue
}

// 第二步:给 EnvironmentValues 扩展一个属性,方便用 KeyPath 访问
extension EnvironmentValues {
   var themeColor: Color {
       get { self[ThemeColorKey.self] }
       set { self[ThemeColorKey.self] = newValue }
   }
}

// 注入
ContentView()
   .environment(\.themeColor, .red)

// 读取
struct MyButton: View {
   @Environment(\.themeColor) var themeColor

   var body: some View {
       Button("点击") {}
           .foregroundColor(themeColor)
   }
}

@Environment 的本质

@EnvironmentObject 一样,底层也是一个按 key 查找的字典, 只不过 key 不是类型,而是 EnvironmentValues 上的 KeyPath

@propertyWrapper
public struct Environment<Value> {
   // \.colorScheme 这样的 KeyPath 就是 key
   public init(_ keyPath: KeyPath<EnvironmentValues, Value>)

   // 你读 colorScheme 的时候,实际上是从当前 View 所在的环境字典里查找
   public var wrappedValue: Value { get }
}

使用 @Environment 时需要关心的问题

  1. @Environment 是只读的wrappedValue 只有 getter,你不能直接写 colorScheme = .dark。想改变环境值只能通过 .environment() 修饰符在外层覆盖。

  2. 和 @EnvironmentObject 的选择

  • 如果是外观、系统设置、能力开关类的数据,优先考虑自定义 EnvironmentKey + @Environment,它有默认值,不会崩溃,更安全。
  • 如果是业务数据、需要触发 UI 更新的对象,用 @EnvironmentObject
  1. @Environment 本身不驱动刷新:它读取的是一个快照值,不是 ObservableObject,当环境值变化时 SwiftUI 会重新渲染 View,但你不能在 @Environment 的值上接 Combine 链。如果需要响应式监听,那是 @EnvironmentObject 的职责。