React Native 实现主题功能

编辑于2020年01月15日

iOS 13 和 Android Q 都推出了深色模式,随着系统级别的支持,越来越多的主流应用也开始陆续退出深色模式。那么在 React Native 中如何实现主题功能呢?本文提供了一种思路,基于 React Hook 和 Context API。

添加主题配置文件

首先添加内置的主题配置文件,默认支持深色和浅色两种。

// 深色
const brandPrimary = '#de6454'
const white = '#fff'

const colors = {
  // brand
  brand_primary: brandPrimary,

  // font
  heading_color: 'rgba(255,255,255,0.85)', // 标题色
  text_color: 'rgba(255,255,255,0.65)', // 主文本色
  text_color_secondary: 'rgba(255,255,255,0.45)', // 次文本色

  // navigation
  header_tint_color: white,
  header_text_color: white,
  tabbar_bg: '#1d1e21',
  header_bg: '#000102',

  // background
  page_bg: '#000102',

  // button
  btn_bg: brandPrimary,
  btn_text: white,
}

export default {
  colors,
}

我这里只配置了颜色,还可以配置图标、字体等。

创建 ThemeContext

首先创建一个对象保存主题配置信息,同时支持添加主题,这样可以做到下载主题配置文件并导入。

import defaultTheme from './default'
import dark from './dark'

const themes = {
  default: defaultTheme,
  dark,
}

export const addTheme = (key, value) => (themes[key] = value)

创建 ThemeContext 和 Provider,通过 Context 向子组件传递主题名称、主机配置以及 changeTheme 的函数。

export const ThemeContext = React.createContext()

export const ThemeContextProvider = ({ children }) => {
  const [theme, changeTheme] = useState('default')
  return (
    <ThemeContext.Provider
      value={{ theme: themes[theme], themeName: theme, changeTheme }}>
      {children}
    </ThemeContext.Provider>
  )
}

添加 Provider

index.js 中将根组件放到 ThemeContextProvider 下。

function setup() {
  // TODO: 全局的初始化操作,例如初始化 SDK

  const Root = () => (
    <ThemeContextProvider>
      <App />
    </ThemeContextProvider>
  )

  return Root
}

组件中使用

在组件内部通过 useContext 获取 Context 中的主题配置信息,在编写样式的时候使用主题内部配置的变量。如果想要切换主题,可以通过 Context 的 changeTheme 函数。

const Home = props => {
  const { navigation } = props
  const { themeName, theme, changeTheme } = useContext(ThemeContext)

  useStatusBar(navigation, 'light-content')
  const onSwitchTheme = () => {
    if (themeName === 'default') {
      changeTheme('dark')
    } else {
      changeTheme('default')
    }
  }

  return (
    <View style={styles.container}>
      <Text style={[styles.title, { color: theme.colors.heading_color }]}>
        Happy Chinese New Year!
      </Text>
      <Button
        onPress={() => navigation.push('FullScreenBg')}
        style={{ marginVertical: 24 }}>
        Full screen background
      </Button>
      <Button onPress={onSwitchTheme}>
        {themeName === 'default' ? 'Dark mode' : 'Light mode'}
      </Button>
    </View>
  )
}