Skip to content
雲里
里雾

NativeWind 暗色模式深化

mindgym 开发 更新于 2026/4/12

React Native 没有 CSS 引擎,因此 Web 上用 CSS variables 或 prefers-color-scheme 实现暗色模式的路径在 RN 里走不通。本页对比 RN 暗色模式的三种主要方案,解析 MindGym 项目选择”语义色 + NativeWind dark: 前缀”的理由,以及语义色命名策略的设计动机。


概述

暗色模式在移动端已从锦上添花变成基础需求(iOS 13 / Android 10 之后)。RN 的样式系统是 JS 对象而非 CSS,没有 var()@media:root,因此需要在 JS 层面解决主题切换问题。主流方案有三种,各有明确的 trade-off。


工作方式

方案一: Context + StyleSheet

用 React Context 传递主题对象(light/dark 两套色值),组件通过 useContext 读取。

优势: 纯 React,不依赖第三方库;灵活度极高——主题对象可包含任意 token。

劣势:

方案二: CSS Variables(Web 思路)

Web 上暗色模式的最优解:

:root { --bg: #ffffff; }
@media (prefers-color-scheme: dark) { :root { --bg: #0f0f0f; } }
.card { background: var(--bg); }

一套 CSS 规则,变量自动切换,零 JS 开销。但 RN 不支持 CSS variables——RN 的样式系统是 JS 对象,不经过浏览器 CSS 引擎。

NativeWind v4 内部用 React Context 模拟了一种类 CSS variables 机制(让 dark: 前缀能工作),但这和 Web 原生的 CSS variables 是两回事——是 JS 层面的查表替换,不是引擎级变量解析。

方案三: NativeWind dark: 前缀

<View className="bg-white dark:bg-zinc-900">
  <Text className="text-gray-900 dark:text-gray-100">...</Text>
</View>

NativeWind 在编译期为带 dark: 前缀的类名生成两套 StyleSheet。运行时根据 colorScheme 选择应用哪一套。darkMode: 'class' 配置让切换由应用代码控制(而非跟随系统 media query)。

优势:

劣势:

三方案对比

维度Context + StyleSheetCSS VariablesNativeWind dark:
运行时开销高(全树 re-render)零(引擎级)低(编译期+运行时选择)
RN 可用性可用不可用可用
代码侵入度
灵活度极高

MindGym 的实现策略

MindGym 选择了介于方案一和方案三之间的策略:语义色抽象层 + Tailwind 自定义色值

语义色体系

theme/colors.js 定义 light/dark 两套语义色 → tailwind.config.js 注册为自定义 Tailwind 类名 → 业务代码只用语义色:

// 业务代码:只用语义色类名,不用裸 hex 值
<ScrollView className="flex-1 bg-bg">
  <View className="bg-surface rounded-xl border border-surface-border">
    <Text className="text-text-primary">...</Text>
  </View>
</ScrollView>

为什么用语义色而不是裸色值

语义色是一层间接引用——组件说”我要背景色”,不说”我要 #faf9f6”。好处:

代价:多了一层抽象,团队成员要理解命名约定。

类名语义LightDark
bg-bg页面底层背景#faf9f6#0f0f0f
bg-surface卡片/面板#ffffff#1a1a1a
border-surface-border分隔线/边框#e2e0db#2e2e2e
text-text-primary主文本#1a1a1a#f5f5f5
text-text-secondary次级文本#6b7280#9ca3af

darkMode: ‘class’ vs ‘media’

MindGym 需要三选一,所以用 class

数据流

用户点击 "深色"
  → setColorScheme('dark')           // Zustand action
  → settings store 更新 + MMKV 持久化
  → app/_layout.tsx 重渲染
  → useResolvedColorScheme() → 'dark'
  → StatusBar 切换图标色
  → (未来 M6) NativeWind colorScheme → dark: 前缀生效

Flexbox 与语义色配合

设置页典型代码结构:

<Pressable className="flex-row items-center justify-between px-4 py-3">
  <Text className="text-text-primary text-base">简体中文</Text>
  {locale === 'zh' && <Text className="text-primary text-base"></Text>}
</Pressable>

版本说明

本页基于 MindGym M1 阶段(Expo SDK 54, NativeWind v4, Tailwind v3, 2026-04-20)。

参见

参考