rn_for_openharmony_深色模式的实现原理:Context 如何让整个 App 变色

案例项目开源地址:https://atomgit.com/nutpi/wanandroid_rn_openharmony
用户拨动一个开关,整个 App 的颜色就变了。背景从深色变浅色,文字从白色变黑色,所有页面同时生效。
这是怎么做到的?答案是 React Context。
先看效果
在"我的"页面有一个深色模式开关:
<View style={styles.settingRow}>
<Text style={[styles.settingLabel, {color: theme.text}]}>🌙 深色模式</Text>
<Switch value={darkMode} onValueChange={setDarkMode} trackColor={{false: '#767577', true: theme.accent}} />
</View>
Switch 组件
Switch 是 React Native 提供的开关组件,就是那种左右滑动的开关按钮。
value={darkMode} 绑定当前状态。darkMode 为 true 时开关打开(显示在右边),为 false 时开关关闭(显示在左边)。
onValueChange={setDarkMode} 绑定状态更新函数。用户拨动开关时,Switch 会调用这个函数,传入新的布尔值。
trackColor 设置开关轨道的颜色。false 是关闭状态的颜色,true 是打开状态的颜色。我们用 theme.accent(强调色)作为打开状态的颜色,让它更醒目。
darkMode 和 setDarkMode 从哪来
const {theme, darkMode, setDarkMode} = useTheme();
从 useTheme Hook 获取。这个 Hook 返回主题相关的状态和方法,任何组件都可以调用它来获取当前主题。
Context 的基本概念
React 的数据流是单向的:从父组件流向子组件,通过 props 传递。但如果一个数据需要被很多组件使用,一层层传 props 会很麻烦。
Context 提供了一种"跨层级"传递数据的方式。你在顶层设置一个值,任何子组件都可以直接获取,不需要中间组件帮忙传递。
主题就是典型的场景:几乎所有组件都需要知道当前是深色还是浅色模式,用 Context 最合适。
ThemeContext 的定义
import React, {createContext, useContext, useState, ReactNode} from 'react';
import {Theme} from '../types';
interface ThemeContextType {
darkMode: boolean;
setDarkMode: (value: boolean) => void;
theme: Theme;
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
接口定义
ThemeContextType 定义了 Context 里有什么内容:
darkMode:布尔值,表示当前是否是深色模式setDarkMode:函数,用于切换深色模式theme:主题对象,包含各种颜色值
TypeScript 的接口让代码更清晰,也能在编译时检查类型错误。
createContext
createContext<ThemeContextType | undefined>(undefined) 创建一个 Context 对象。
泛型 <ThemeContextType | undefined> 指定 Context 的值类型。为什么要加 undefined?因为 Context 的默认值是 undefined,在 Provider 外面使用时会是 undefined。
参数 undefined 是默认值。如果组件在 Provider 外面使用 Context,就会得到这个默认值。
ThemeProvider 组件
export const ThemeProvider = ({children}: {children: ReactNode}) => {
const [darkMode, setDarkMode] = useState(true);
const theme: Theme = {
bg: darkMode ? '#0f0f23' : '#f5f5f5',
card: darkMode ? '#1a1a2e' : '#ffffff',
text: darkMode ? '#ffffff' : '#333333',
subText: darkMode ? '#888888' : '#666666',
border: darkMode ? '#2a2a4a' : '#e0e0e0',
accent: '#6c5ce7',
success: '#6bcb77',
danger: '#ff6b6b',
};
return (
<ThemeContext.Provider value={{darkMode, setDarkMode, theme}}>
{children}
</ThemeContext.Provider>
);
};
Provider 的作用
ThemeContext.Provider 是 Context 的提供者。它包裹的所有子组件都可以访问 Context 的值。
value={{darkMode, setDarkMode, theme}} 设置 Context 的值。这个对象会被传递给所有使用 useTheme 的组件。
children 参数
{children}: {children: ReactNode} 接收子组件。ReactNode 是 React 元素的类型,可以是组件、字符串、数字、null 等。
Provider 把 children 原样渲染出来,只是在外面包了一层 Context。
darkMode 状态
useState(true) 初始值是 true,意味着 App 默认是深色模式。如果想默认浅色,改成 useState(false)。
theme 对象的计算
const theme: Theme = {
bg: darkMode ? '#0f0f23' : '#f5f5f5',
card: darkMode ? '#1a1a2e' : '#ffffff',
// ...
};
theme 对象根据 darkMode 的值动态计算。每个颜色都用三元表达式:深色模式用第一个值,浅色模式用第二个值。
这个对象在每次渲染时都会重新计算。当 darkMode 变化时,theme 的值也会变化,使用 theme 的组件就会重新渲染。
颜色的设计
深色模式:
bg: '#0f0f23'深蓝黑色背景,比纯黑更柔和card: '#1a1a2e'卡片背景比页面背景稍浅,有层次感text: '#ffffff'白色文字subText: '#888888'灰色次要文字
浅色模式:
bg: '#f5f5f5'浅灰色背景,比纯白更护眼card: '#ffffff'白色卡片text: '#333333'深灰色文字,比纯黑更柔和subText: '#666666'中灰色次要文字
accent、success、danger 在两种模式下保持一致,因为这些是功能色,需要保持辨识度。
useTheme Hook
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
};
useContext
useContext(ThemeContext) 获取最近的 ThemeContext.Provider 的值。如果组件在 Provider 外面,返回 createContext 时设置的默认值(undefined)。
错误检查
if (!context) 检查是否在 Provider 外面使用。如果是,抛出错误提示开发者。这是一个常见的模式,可以在开发时尽早发现问题。
错误信息 'useTheme must be used within ThemeProvider' 明确告诉开发者问题在哪,怎么解决。
返回值
返回 context,包含 darkMode、setDarkMode、theme。调用方可以解构获取需要的部分。
在 App 顶层使用 Provider
// App.tsx
import {ThemeProvider} from './src/context/ThemeContext';
const App = () => {
return (
<ThemeProvider>
<AuthProvider>
{/* 其他组件 */}
</AuthProvider>
</ThemeProvider>
);
};
Provider 的位置
ThemeProvider 要放在最外层,这样所有组件都能访问主题。如果放在里面,外面的组件就访问不到了。
多个 Provider 可以嵌套。我们有 ThemeProvider 和 AuthProvider,它们各自管理不同的状态,互不干扰。
组件中使用主题
const {theme} = useTheme();
<View style={{backgroundColor: theme.bg}}>
<Text style={{color: theme.text}}>Hello</Text>
</View>
动态样式
style={{backgroundColor: theme.bg}} 把主题颜色应用到样式上。当主题切换时,theme.bg 的值变化,组件重新渲染,背景色就变了。
这种写法把样式写在 JSX 里,适合动态样式。静态样式建议用 StyleSheet.create,性能更好。
混合使用
<View style={[styles.card, {backgroundColor: theme.card, borderColor: theme.border}]}>
style 接受数组,React Native 会合并这些样式。styles.card 是静态样式(圆角、内边距等),后面的对象是动态样式(颜色)。
后面的样式会覆盖前面的同名属性,所以动态样式放后面。
主题切换的完整流程
- 用户拨动 Switch 开关
- Switch 调用
setDarkMode(newValue) - ThemeProvider 里的
darkMode状态更新 theme对象重新计算,颜色值变化- ThemeContext.Provider 的 value 变化
- 所有使用
useTheme的组件收到通知 - 这些组件重新渲染,应用新的颜色
- 用户看到整个 App 的颜色变了
整个过程是自动的,你只需要调用 setDarkMode,React 和 Context 会处理剩下的事情。
Theme 类型定义
// types/index.ts
export interface Theme {
bg: string;
card: string;
text: string;
subText: string;
border: string;
accent: string;
success: string;
danger: string;
}
为什么要定义类型
TypeScript 的类型定义有几个好处:
- 编辑器自动补全:输入
theme.会提示所有可用的颜色 - 编译时检查:如果写错了颜色名(比如
theme.txt),编译时就会报错 - 代码文档:看类型定义就知道有哪些颜色可用
颜色的命名
用语义化的名字而不是具体颜色名。text 而不是 white,bg 而不是 darkBlue。这样切换主题时不用改名字,只改值就行。
持久化主题设置
当前实现有一个问题:用户切换到浅色模式,关闭 App 再打开,又变回深色了。因为 darkMode 的初始值是 true。
如果要持久化,可以用 AsyncStorage:
import AsyncStorage from '@react-native-async-storage/async-storage';
export const ThemeProvider = ({children}: {children: ReactNode}) => {
const [darkMode, setDarkMode] = useState(true);
useEffect(() => {
AsyncStorage.getItem('darkMode').then(value => {
if (value !== null) {
setDarkMode(value === 'true');
}
});
}, []);
const handleSetDarkMode = (value: boolean) => {
setDarkMode(value);
AsyncStorage.setItem('darkMode', String(value));
};
// ...
};
读取保存的设置
useEffect 在组件挂载时执行,从 AsyncStorage 读取之前保存的设置。AsyncStorage.getItem 返回字符串,需要转换成布尔值。
保存设置
handleSetDarkMode 包装了 setDarkMode,在更新状态的同时保存到 AsyncStorage。
我们的项目没有实现持久化,保持简单。如果你的项目需要,可以参考上面的代码。
完整的 ThemeContext 代码
import React, {createContext, useContext, useState, ReactNode} from 'react';
import {Theme} from '../types';
interface ThemeContextType {
darkMode: boolean;
setDarkMode: (value: boolean) => void;
theme: Theme;
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
export const ThemeProvider = ({children}: {children: ReactNode}) => {
const [darkMode, setDarkMode] = useState(true);
const theme: Theme = {
bg: darkMode ? '#0f0f23' : '#f5f5f5',
card: darkMode ? '#1a1a2e' : '#ffffff',
text: darkMode ? '#ffffff' : '#333333',
subText: darkMode ? '#888888' : '#666666',
border: darkMode ? '#2a2a4a' : '#e0e0e0',
accent: '#6c5ce7',
success: '#6bcb77',
danger: '#ff6b6b',
};
return (
<ThemeContext.Provider value={{darkMode, setDarkMode, theme}}>
{children}
</ThemeContext.Provider>
);
};
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
};
Context 是 React 状态管理的基础,理解了它,Redux、MobX 这些库也更容易理解。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)