ReactNativeStylingSkill rn-styling

React Native 样式指南,包含 NativeWind 和 BrandColors 的使用模式,适用于 Expo/React Native 中的样式、主题、颜色、响应式布局或平台特定 UI 开发。

移动开发 0 次安装 0 次浏览 更新于 3/3/2026

name: rn-styling description: React Native 样式指南

React Native 样式指南

问题说明

React Native 样式与 Web CSS 有本质的不同。NativeWind 弥补了这一差距,但也有自己的规则。此代码库使用混合方法:BrandColors 用于语义化颜色,NativeWind 用于布局工具类。


模式:BrandColors 与 NativeWind 类

规则: 使用 BrandColors 进行语义化颜色定义,使用 NativeWind 进行布局/间距定义。

// ✅ 正确:混合方法
<View className="flex-1 p-4 rounded-lg" style={{ backgroundColor: BrandColors.background }}>
  <Text className="text-lg font-semibold" style={{ color: BrandColors.textPrimary }}>
    标题
  </Text>
</View>

// ❌ 错误:硬编码十六进制颜色(违规扫描器会阻止此操作)
<View className="flex-1 p-4 bg-[#1a1a2e]">

// ❌ 错误:使用 NativeWind 颜色类进行品牌颜色定义
<View className="flex-1 p-4 bg-blue-500">

// ✅ 可接受:NativeWind 品牌别名(如果已配置)
<View className="flex-1 p-4 bg-brand-blue">

何时使用哪个:

使用场景 方法
品牌颜色(主要,次要) BrandColors.primary
背景颜色 BrandColors.background
文本颜色 BrandColors.textPrimary, textSecondary
布局(flex, padding, margin) NativeWind 类
边框,圆角 NativeWind 类
阴影 样式对象(NativeWind 阴影在 iOS 上有限)

模式:主题感知颜色

问题: 支持使用 BrandColors 的浅色/深色模式。

// BrandColors.ts 导出两种主题
import { BrandColors, BrandColorsDark } from '@/constants/BrandColors';

// 当前主题颜色的钩子
import { useColorScheme } from 'react-native';

function useThemeColors() {
  const colorScheme = useColorScheme();
  return colorScheme === 'dark' ? BrandColorsDark : BrandColors;
}

// 组件使用
function ThemedCard({ title }: { title: string }) {
  const colors = useThemeColors();
  
  return (
    <View 
      className="p-4 rounded-lg"
      style={{ backgroundColor: colors.cardBackground }}
    >
      <Text style={{ color: colors.textPrimary }}>{title}</Text>
    </View>
  );
}

模式:NativeWind 类排序

问题: 与 Web CSS 不同,React Native 不支持层叠样式。对于冲突属性,最后一个类获胜。

// 类排序很重要!
<View className="p-4 p-2" />  // p-2 获胜(最后)
<View className="p-2 p-4" />  // p-4 获胜(最后)

// 条件类 - 明确
<View className={`p-4 ${isCompact ? 'p-2' : ''}`} />  
// 如果 isCompact: "p-4 p-2" → p-2 获胜 ✅

// 合并 className 属性
interface Props {
  className?: string;
}

function Card({ className }: Props) {
  // 父类覆盖默认值(它们最后出现)
  return <View className={`p-4 rounded-lg ${className ?? ''}`} />;
}

// 使用:<Card className="p-8" />  → p-8 覆盖 p-4

模式:平台特定样式

import { Platform, StyleSheet } from 'react-native';

// 选项 1: Platform.select
const styles = StyleSheet.create({
  shadow: Platform.select({
    ios: {
      shadowColor: '#000',
      shadowOffset: { width: 0, height: 2 },
      shadowOpacity: 0.1,
      shadowRadius: 4,
    },
    android: {
      elevation: 4,
    },
  }),
});

// 选项 2: Platform.OS 检查
<View style={Platform.OS === 'ios' ? styles.iosShadow : styles.androidShadow} />

// 选项 3: NativeWind 平台前缀
<View className="ios:pt-12 android:pt-8" />

模式:安全区域处理

import { SafeAreaView } from 'react-native-safe-area-context';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

// 选项 1: SafeAreaView 包装器(最简单)
function Screen() {
  return (
    <SafeAreaView className="flex-1" edges={['top', 'bottom']}>
      <Content />
    </SafeAreaView>
  );
}

// 选项 2: 手动内边距(更多控制)
function Screen() {
  const insets = useSafeAreaInsets();
  
  return (
    <View 
      className="flex-1"
      style={{ paddingTop: insets.top, paddingBottom: insets.bottom }}
    >
      <Content />
    </View>
  );
}

// 选项 3: NativeWind 安全区域工具类(如果已配置)
<View className="flex-1 pt-safe pb-safe">

模式:键盘避免

import { KeyboardAvoidingView, Platform } from 'react-native';

function FormScreen() {
  return (
    <KeyboardAvoidingView
      className="flex-1"
      behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
      keyboardVerticalOffset={Platform.OS === 'ios' ? 64 : 0} // 调整标题栏
    >
      <ScrollView className="flex-1">
        <TextInput />
        <TextInput />
        <SubmitButton />
      </ScrollView>
    </KeyboardAvoidingView>
  );
}

模式:响应式断点

注意: NativeWind v2 断点与 Web Tailwind 不同。

// NativeWind v2 断点(基于窗口宽度)
// sm: 640px, md: 768px, lg: 1024px, xl: 1280px

// 响应式内边距
<View className="p-2 sm:p-4 md:p-6" />

// 响应式 flex 方向
<View className="flex-col sm:flex-row" />

// 程序性检查屏幕尺寸
import { useWindowDimensions } from 'react-native';

function ResponsiveLayout() {
  const { width } = useWindowDimensions();
  const isTablet = width >= 768;
  
  return isTablet ? <TabletLayout /> : <PhoneLayout />;
}

模式:动画样式

问题: 避免使用 Animated 值时的重新渲染。

import { Animated } from 'react-native';

function FadeInCard() {
  // useRef 持久化 Animated.Value 跨渲染
  const fadeAnim = useRef(new Animated.Value(0)).current;
  
  useEffect(() => {
    Animated.timing(fadeAnim, {
      toValue: 1,
      duration: 300,
      useNativeDriver: true, // 动画 opacity/transform 时总是使用
    }).start();
  }, []);
  
  return (
    <Animated.View 
      className="p-4 rounded-lg"
      style=[
        { backgroundColor: BrandColors.cardBackground },
        { opacity: fadeAnim }, // 动画样式在数组中
      ]
    >
      <Text>内容</Text>
    </Animated.View>
  );
}

样式数组: 结合静态 + 动画样式。

// ✅ 正确:样式数组
style={[styles.card, { opacity: fadeAnim }]}

// ❌ 错误:展开(每次渲染都会创建新对象)
style={{ ...styles.card, opacity: fadeAnim }}

模式:StyleSheet 与内联

// 使用 StyleSheet 的情况:
// - 跨渲染重复使用的复杂样式
// - 具有多个属性的样式
// - 性能关键组件

const styles = StyleSheet.create({
  card: {
    padding: 16,
    borderRadius: 12,
    backgroundColor: BrandColors.cardBackground,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
});

// 使用内联/NativeWind 的情况:
// - 简单的布局工具类
// - 一次性样式
// - 条件样式

<View className="flex-1 p-4" />
<View style={{ marginTop: dynamicValue }} />

品牌颜色模式

创建一个集中的颜色常量文件:

// constants/BrandColors.ts
export const BrandColors = {
  primary: '#...',
  secondary: '#...',
  background: '#...',
  cardBackground: '#...',
  textPrimary: '#...',
  textSecondary: '#...',
  // ... 等
};

export const BrandColorsDark = {
  // 深色模式变体
};

推荐:违规扫描器

考虑添加违规扫描器以阻止:

  • 硬编码十六进制颜色(除了允许的例外)
  • 直接颜色字符串

NativeWind 注释

如果使用 NativeWind v2(而不是 v4),请留意这些差异:

  • RN 组件上的 className 属性
  • 有限的 Web Tailwind 兼容性
  • 一些工具类不受支持

常见问题

问题 解决方案
颜色不适用 检查 BrandColors 导入,验证主题上下文
NativeWind 类被忽略 不是所有的 Tailwind 工具类都有效 - 检查 v2 文档
阴影不显示(iOS) 使用 StyleSheet 与 shadowColor/Offset/Opacity/Radius
阴影不显示(Android) 使用 elevation 属性
安全区域不被尊重 包装在 SafeAreaView 中或使用内边距
样式在挂载时闪烁 使用 Animated 进行过渡

推荐文件结构

constants/
  BrandColors.ts       # 颜色定义
  designSystem.ts      # 间距,排版比例
components/
  ui/Card.tsx          # 示例混合样式
app/
  _layout.tsx          # 主题提供者设置