CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
React Native 开发入门/课时/平台差异
RawBlame
课时目录12 节

React Native 开发入门

  1. 01RN 与 Expo
  2. 02核心组件
  3. 03布局与列表
  4. 04平台差异
  5. 05导航与手势
  6. 06State 与 JSX
  7. 07网络
  8. 08本地存储
  9. 09原生模块
  10. 10动画
  11. 11性能优化
  12. 12测试与发布

平台差异

Platform 模块与权限。

30 分钟

平台差异处理

这一课你将能做到

  1. 用 Platform.OS 和 Platform.select 写出 iOS / Android 各自得体的样式和逻辑分支
  2. 用 SafeAreaView 正确处理刘海屏、底部 Home 指示条,并配置两端状态栏
  3. 处理阴影、返回行为、平台专属文件这几类最常见的双端差异

为什么要学

RN 号称「一套代码两端跑」,但不是「一套代码两端长得一模一样」。iOS 和 Android 是两套设计语言:iOS 导航栏标题居中、靠右滑手势返回;Android 标题靠左、有系统返回键。iOS 的阴影可以调颜色调模糊,Android 的阴影只有一个「海拔」数字。你不主动处理这些差异,App 总有一端看起来「不对劲」——用户说不上来哪里怪,但会觉得这 App 不精致。

人话:同一份简历投不同公司要微调措辞,同一份代码也要按平台微调表现——90% 的逻辑共用,10% 的细节差异化。这一课就是教你那 10% 怎么处理得优雅,而不是写一堆 if-else 把代码搞成意大利面。

先建立图像

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

export default function App() {
  return (
    <View style={styles.header}>
      <Text style={styles.title}>当前平台:{Platform.OS}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  header: {
    paddingTop: Platform.OS === 'ios' ? 60 : 40,  // iOS 刘海区域更高
    backgroundColor: '#007bff',
    alignItems: Platform.OS === 'ios' ? 'center' : 'flex-start', // 标题 iOS 居中,Android 靠左
    paddingHorizontal: 16,
    paddingBottom: 12,
  },
  title: { color: '#fff', fontSize: 18 },
});

同一份代码,iOS 上标题居中且顶部留白更多,Android 上标题靠左。Platform.OS 就是这一切分支的起点,它的值是字符串:'ios'、'android'、'web'。

平台差异的四层处理手段,从轻到重:

样式小差异 → Platform.OS 三元 / Platform.select
组件结构差异 → 条件渲染 {Platform.OS === 'ios' && <IOSOnlyView />}
整块实现差异 → 平台专属文件 MyButton.ios.js / MyButton.android.js
系统能力差异 → Expo 模块内部已抹平(如 expo-haptics 两端震动)

跟着做

步骤 1:Platform.OS 条件判断

import { Platform } from 'react-native';

if (Platform.OS === 'ios') {
  // iOS 专属逻辑
} else if (Platform.OS === 'android') {
  // Android 专属逻辑
}

// 还能拿到系统版本号,做更细的判断
console.log(Platform.Version); // iOS 是字符串 '17.2',Android 是 API 数字 34

典型用途:iOS 14 以下不支持某个特性时降级处理,Android 低版本关掉某个动画。

步骤 2:Platform.select 优雅写样式

样式分支多了,三元表达式会很难看,Platform.select 是声明式的写法:

const styles = StyleSheet.create({
  container: {
    paddingTop: Platform.select({ ios: 60, android: 40, default: 20 }),
    fontFamily: Platform.select({ ios: 'System', android: 'Roboto' }),
  },
});

它比三元表达式清晰,还多了 default 兜底(Web 端命中)。它也能返回任意值,不只数字:

const DecayConfig = Platform.select({ ios: { deceleration: 0.998 }, // iOS 滚动惯性参数 android: { deceleration: 0.985 }, });

步骤 3:阴影——双端最大的样式坑

这是初学者 100% 会踩的坑:你写的阴影在 iOS 上好好的,到 Android 上完全消失。因为两端的阴影实现机制根本不同:

const styles = StyleSheet.create({
  card: {
    backgroundColor: '#fff',   // 必须有背景色,否则 Android 阴影出不来
    borderRadius: 12,
    // iOS 阴影:shadow* 四件套,缺一不可
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 8,
    // Android 阴影:只有 elevation 一个属性
    elevation: 4,
  },
});

iOS 用 shadow 系列精细控制(颜色、偏移、透明度、模糊半径);Android 只有 elevation(海拔高度),数字越大阴影越重,颜色和模糊由系统根据 Material 规范统一计算,你控制不了。所以两端阴影永远不会像素级一致,视觉接近即可。

Android 阴影还有两个隐藏条件:父容器不能设 overflow: 'hidden'(会裁掉阴影),卡片本身必须有非透明背景色。

步骤 4:安全区域 SafeAreaView

iPhone 有刘海和底部 Home 横条,Android 全面屏有手势条。内容直接铺满屏幕,顶部会被刘海吃掉一截,底部按钮会被横条挡住。

npx expo install react-native-safe-area-context
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';

export default function App() {
  return (
    <SafeAreaProvider>
      <SafeAreaView style={{ flex: 1 }} edges={['top', 'bottom']}>
        {/* 内容自动避开刘海和底部横条 */}
      </SafeAreaView>
    </SafeAreaProvider>
  );
}

两个注意点:第一,必须用 react-native-safe-area-context 这个社区库,RN 自带的 SafeAreaView 只对 iOS 有效,Android 上等于一个普通 View;第二,库要求最外层包 SafeAreaProvider,忘了包则完全不生效。

只想躲刘海不想管底部?用 edges 精确控制:edges={['top']}。想拿到具体数值自己算布局?用 Hook:

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

const insets = useSafeAreaInsets();
// insets = { top: 59, bottom: 34, left: 0, right: 0 }(iPhone 14 Pro 实测值)
<View style={{ paddingTop: insets.top }} />

步骤 5:状态栏 StatusBar

状态栏就是屏幕顶部显示时间、电量的那条。你的页面背景会延伸到它下面,所以要控制它的图标颜色:

import { StatusBar } from 'react-native';

<StatusBar barStyle="dark-content" backgroundColor="#fff" />
  • barStyle="dark-content":图标深色,配浅色背景;深色背景反过来用 "light-content"
  • backgroundColor:只对 Android 生效(iOS 状态栏永远透明,背景色由你的页面决定)

深色页面忘记切 light-content,状态栏图标会被深色背景淹没看不见——这是上架截图常见减分项。

步骤 6:平台专属文件

当某个组件两端实现差异太大,塞进一个文件里全是 Platform 分支时,直接拆成两个文件,RN 打包时自动按平台选择:

components/
├── MyButton.ios.js      # iOS 打包时只带它
└── MyButton.android.js  # Android 打包时只带它
import MyButton from './components/MyButton'; // 不写后缀,自动匹配

步骤 7:Android 返回键 BackHandler

iOS 用户习惯右滑返回(React Navigation 自带手势),Android 用户会按系统返回键。默认行为是退出页面栈,但有些页面你想拦截——比如表单填了一半,返回前要弹确认:

import { useEffect } from 'react';
import { BackHandler, Alert } from 'react-native';

useEffect(() => {
  const sub = BackHandler.addEventListener('hardwareBackPress', () => {
    Alert.alert('放弃编辑?', '已填写的内容将丢失', [
      { text: '继续编辑', style: 'cancel' },
      { text: '放弃', onPress: () => navigation.goBack() },
    ]);
    return true; // 返回 true = 拦截,不执行默认返回
  });
  return () => sub.remove();
}, []);

返回 true 表示「我处理了,系统别动」,返回 false 则走默认行为。用 React Navigation 时也可以用它自带的 beforeRemove 事件,跨端更一致。

讲清楚要点

  1. 差异最小化原则:能共用的样式坚决共用,只有确有必要才写 Platform 分支。每多一个分支,未来维护就要多测一遍两端。
  2. 阴影必须写两套:shadow* 四件套管 iOS,elevation 管 Android,缺一个某端就没阴影。Android 侧还要求背景色非透明、父容器不裁剪。
  3. SafeArea 是必做项:现在几乎没有非全面屏手机,不做安全区域适配,内容必然被刘海或手势条遮挡。用社区库 react-native-safe-area-context,别用 RN 自带的。
  4. 返回行为两端不同:iOS 是右滑手势,Android 是系统返回键/手势。Android 需要拦截时用 BackHandler,返回 true 拦截。
  5. Platform.select 优于三元:可读性好,支持 default 兜底,打包工具还能对未选中的平台分支做裁剪。
  6. 平台专属文件是最后的手段:.ios.js / .android.js 适合整块实现都不同的组件,小差异别用它,会多出一倍要维护的文件。

你可能会懵的地方

  • 为什么 iOS 的阴影在 Android 上完全没效果? Android 根本不解析 shadow* 属性,它只认 elevation。两个都要写,这不是 bug 是平台机制——iOS 的 Core Animation 开放阴影细节,Android 的 Material 体系把阴影收归系统统一渲染。

  • elevation 在 Android 上有时失效? 三个排查方向:父容器有没有 overflow: 'hidden'(会裁掉)、卡片有没有背景色(透明背景不出阴影)、相邻元素是不是盖住了它(elevated 元素会被不透明的兄弟元素遮挡)。

  • 要不要让两端长得完全一样? 不要强求。遵循各平台规范(iOS 人机界面指南、Android Material Design)反而让用户觉得「这 App 很专业」。只有品牌强相关的页面(启动页、营销页、会员页)才值得追求像素级一致。

  • Platform.OS 还能是什么值? 还有 'web'(Expo 支持 Web 端)、'macos'、'windows'(桌面端扩展),日常只关心 ios / android。

  • 权限申请也算平台差异吗? 算,而且是重灾区。iOS 权限弹窗只弹一次,Android 分细粒度权限且各厂商还有定制(小米的自启动、华为的后台管理)。Expo 模块帮你处理了大部分声明工作,具体申请流程第 9 课原生能力里展开讲。

错法对照

错法问题正解
只写 shadow* 不写 elevationAndroid 无阴影两套都写
用 react-native 自带 SafeAreaViewAndroid 上完全无效用 react-native-safe-area-context
顶部 padding 写死 44不同机型刘海高度不同,错漏百出SafeAreaView 或 useSafeAreaInsets 自动适配
大量三元表达式堆样式不可读难维护Platform.select 或拆平台文件
强行让 Android 标题居中违背平台习惯,用户别扭iOS 居中 / Android 靠左
忽略 Android 返回键表单页一按返回直接丢数据BackHandler 或 beforeRemove 拦截确认
深色页面忘切 light-content状态栏图标隐身barStyle 跟背景反着来
SafeAreaView 生效但不全没包 SafeAreaProvider应用根部包一层

卡点急救

  1. Android 阴影不显示:检查父容器 overflow: 'hidden'、卡片背景色是否透明、是否被兄弟元素盖住。
  2. SafeAreaView 没效果:确认装的是 react-native-safe-area-context(不是 RN 自带的),且根部包了 SafeAreaProvider,改完按 r 重载。
  3. 状态栏文字被背景淹没:浅色背景用 barStyle="dark-content",深色背景用 "light-content"。
  4. 拦截返回键后整个 App 退不出:确认只在需要的页面 addEventListener,且卸载时 sub.remove(),否则监听器泄漏会影响其他页面。

面试常问

  • RN 如何处理平台差异? 四层手段:Platform.OS / Platform.Version 运行时判断、Platform.select 声明式选择、平台后缀文件(.ios.js/.android.js)编译期拆分、以及 Expo / 第三方库内部已封装好的差异抹平。原则是差异最小化,按代价从低到高选用。

  • iOS 和 Android 阴影实现为什么不同? iOS 的 Core Animation 支持完整的阴影参数(颜色、偏移、模糊半径),由开发者精细控制;Android 的 Material Design 体系里阴影是「海拔 elevation」的物理推论,系统统一计算渲染,不开放细节参数。所以 RN 只能暴露两套 API,双写是必然的。

  • 如何做机型适配? 安全区域交给 SafeAreaView;布局用 flex 和 useWindowDimensions 响应屏幕尺寸而非写死像素;字号尊重系统字体缩放设置(Text 的 allowFontScaling);折叠屏等极端形态按需用窗口尺寸断点处理。核心思想:用相对布局代替绝对数值。

自测三问

  1. iOS 和 Android 的阴影分别用什么属性?Android 出阴影还需要什么前提条件?
  2. 为什么推荐 react-native-safe-area-context 而不是 RN 自带的 SafeAreaView?
  3. 想拦截 Android 返回键弹确认框,用什么 API?返回 true 和 false 各代表什么?

小结一句话

平台差异的正确姿势是「大统一、小分化」:逻辑共用,用 Platform.select、平台文件、安全区域和 BackHandler 把两端各 10% 的细节做到位。

延伸

  • Platform 官方文档
  • react-native-safe-area-context 文档
  • 下一课:导航与手势——页面之间怎么跳、参数怎么传、骨架怎么搭。
加载本节练习…
← 上一课布局与列表下一课 →导航与手势