Различия iOS и Android, о которых спрашивают

«Один код на две платформы» — правда лишь отчасти. Интервьюер обязательно спросит, где именно платформы расходятся.

Platform-specific код — нормальная часть React Native. Вопрос не в том, есть ли он, а в том, аккуратно ли он изолирован.

Вопрос

«Какие различия между iOS и Android приходилось учитывать? Как организовать платформенный код?»

Что на самом деле проверяет интервьюер

Реальный опыт. Человек, который довёл хотя бы одно приложение до сторов, назовёт эти различия сходу — они бьют по рукам на первой же неделе. Теоретик расскажет про Platform.OS и остановится.

Развёрнутый ответ

Три способа развести платформы

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

// 1. Точечная проверка
const paddingTop = Platform.OS === 'ios' ? 20 : 0;

// 2. Platform.select — самый читаемый вариант для стилей
const styles = StyleSheet.create({
  card: {
    backgroundColor: '#fff',
    borderRadius: 12,
    ...Platform.select({
      ios: {
        shadowColor: '#000',
        shadowOffset: { width: 0, height: 2 },
        shadowOpacity: 0.15,
        shadowRadius: 6,
      },
      android: { elevation: 4 },
    }),
  },
});

// 3. Проверка версии ОС — например, для API, появившихся в Android 13
const canAskNotifications = Platform.OS === 'android' && Platform.Version >= 33;

Четвёртый способ — отдельные файлы. Metro сам выберет нужный:

components/
  Picker.ios.tsx
  Picker.android.tsx
  Picker.tsx        # запасной вариант, например для web
# импорт остаётся общим:
# import Picker from './components/Picker';

Различия, которые встречаются в каждом проекте

ТемаiOSAndroid
Тениshadow*elevation
Кнопка «Назад»жест свайпа от краяаппаратная кнопка, BackHandler
КлавиатураKeyboardAvoidingView с paddingчаще height или настройки windowSoftInputMode
Ripple при нажатиинет, обычно меняют прозрачностьandroid_ripple у Pressable
Шрифтыпо имени семействапо имени файла шрифта
РазрешенияInfo.plist и запрос при первом использованииAndroidManifest и рантайм-запрос
Статус-барпрозрачный, контент под нимзанимает место, есть translucent

Безопасные зоны

Вырез камеры, «чёлка», индикатор жеста снизу, у Android — вырезы и навигационная панель. Вручную это не считают: берут react-native-safe-area-context.

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

function Screen({ children }) {
  const insets = useSafeAreaInsets();
  return (
    <View style={{ flex: 1, paddingTop: insets.top, paddingBottom: insets.bottom }}>
      {children}
    </View>
  );
}

Обратная кнопка Android

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

useEffect(() => {
  const sub = BackHandler.addEventListener('hardwareBackPress', () => {
    if (isModalOpen) {
      closeModal();
      return true; // событие обработано, экран не закрывается
    }
    return false;  // поведение по умолчанию
  });
  return () => sub.remove();
}, [isModalOpen]);

Клавиатура

<KeyboardAvoidingView
  style={{ flex: 1 }}
  behavior={Platform.OS === 'ios' ? 'padding' : undefined}
  keyboardVerticalOffset={headerHeight}
>
  {form}
</KeyboardAvoidingView>

На Android чаще помогает настройка android:windowSoftInputMode="adjustResize" в манифесте, а не сам компонент. Хороший ответ включает эту деталь.

Как это организовать в проекте

Главный принцип — не рассыпать Platform.OS по всему коду. Платформенные различия прячут внутрь общих компонентов дизайн-системы: экраны используют <Card> и <Screen>, а уже внутри них решается, тень это или elevation. Это то, что интервьюер хочет услышать от middle+.

Типичные ошибки кандидатов

  • Знают только Platform.OS и не слышали про Platform.select и файлы с платформенным расширением.
  • Забывают, что elevation на Android не только рисует тень, но и влияет на порядок наложения.
  • Тестируют только на iPhone симуляторе и потом «внезапно» находят проблемы с клавиатурой и вырезами на Android.
  • Не упоминают разрешения, хотя рантайм-запросы на Android — источник половины багов в релизе.

Как ответить кратко

«Развожу платформы четырьмя способами: Platform.OS для точечных проверок, Platform.select для стилей, Platform.Version для версионных API и отдельные файлы .ios и .android, которые Metro подставляет сам. Регулярно расходятся тени (shadow против elevation), безопасные зоны — беру safe-area-context, клавиатура: KeyboardAvoidingView на iOS и adjustResize на Android, аппаратная кнопка «Назад» через BackHandler, ripple при нажатии, шрифты и разрешения. Стараюсь прятать различия внутрь компонентов дизайн-системы, а не размазывать проверки по экранам.»

Проверьте себя
1. Как правильно сделать тень на обеих платформах?
AДостаточно shadowColor и shadowOffset — они работают везде
BНа iOS нужны shadow-свойства, на Android — elevation; обычно их задают через Platform.select
CТени поддерживаются только на iOS, на Android их рисуют картинкой
DНужно свойство boxShadow, как в CSS
2. Что делает файл Button.ios.tsx рядом с Button.android.tsx?
AНичего, это просто соглашение об именовании для читаемости
BMetro автоматически подставит нужный файл при импорте './Button' в зависимости от платформы сборки
CФайлы объединяются в один компонент при сборке
DТакие файлы игнорируются, пока не указать их в metro.config.js
3. Почему аппаратная кнопка «Назад» — важная тема на собеседовании по RN?
AОна существует только на Android, и её поведение приходится обрабатывать отдельно через BackHandler
BОна блокирует JS-поток при каждом нажатии
CОна одинаково работает на обеих платформах, тема не важна
DОна отключена в React Native по умолчанию