← React/React Native: мобильная разработка#291 из 383← ПредыдущийСледующий →+30 XP
Полезно по теме:Гайд: React или VueПрактика: React setТермин: React HooksТема: React: хуки и экосистема

React Native: мобильная разработка

Что такое React Native

React Native — это фреймворк от Meta для создания нативных мобильных приложений с помощью JavaScript и React. В отличие от WebView-подходов (Cordova, PhoneGap), React Native компилирует компоненты в настоящие нативные виджеты платформы.

Ключевые идеи:

  • Одна кодовая база для iOS и Android
  • JavaScript-поток общается с нативным через Bridge (или новая архитектура JSI)
  • Нативные компоненты — не HTML-элементы, а View, Text, Image и др.
  • Hot Reload и быстрый цикл разработки
  • Основные компоненты

    В React Native нет <div>, <span> или <p>. Вместо них — нативные аналоги:

    | Web (HTML) | React Native | Что делает |

    |---|---|---|

    | <div> | <View> | Контейнер, flexbox-блок |

    | <span>, <p> | <Text> | Любой текст |

    | <img> | <Image> | Изображение |

    | <input> | <TextInput> | Поле ввода |

    | <button> | <TouchableOpacity> | Кнопка с нажатием |

    | <ul> | <FlatList> | Прокручиваемый список |

    | <div style="overflow:scroll"> | <ScrollView> | Прокручиваемый контейнер |

    import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'
    
    function ProfileCard({ name, role, onPress }) {
      return (
        <View style={styles.card}>
          <Text style={styles.name}>{name}</Text>
          <Text style={styles.role}>{role}</Text>
          <TouchableOpacity style={styles.button} onPress={onPress}>
            <Text style={styles.buttonText}>Подробнее</Text>
          </TouchableOpacity>
        </View>
      )
    }
    
    const styles = StyleSheet.create({
      card: {
        backgroundColor: '#fff',
        borderRadius: 12,
        padding: 16,
        margin: 8,
        // React Native использует flexbox по умолчанию
        // и не поддерживает все CSS-свойства
      },
      name: { fontSize: 18, fontWeight: 'bold', color: '#1a1a1a' },
      role: { fontSize: 14, color: '#666', marginTop: 4 },
      button: { backgroundColor: '#007AFF', borderRadius: 8, padding: 10, marginTop: 12 },
      buttonText: { color: '#fff', textAlign: 'center', fontWeight: '600' },
    })

    StyleSheet: стили в React Native

    CSS в React Native — это объекты JavaScript, а не строки. StyleSheet.create() оптимизирует их и помогает с отладкой:

    // Inline стили (не рекомендуется для частых рендеров)
    <View style={{ flex: 1, backgroundColor: 'red' }} />
    
    // StyleSheet (оптимизировано)
    const styles = StyleSheet.create({
      container: {
        flex: 1,           // занять всё доступное пространство
        flexDirection: 'column',  // направление по умолчанию — column (не row!)
        alignItems: 'center',
        justifyContent: 'center',
        backgroundColor: '#f5f5f5',
      }
    })
    
    // Несколько стилей
    <View style={[styles.container, isActive && styles.active]} />

    Отличия от CSS:

  • Нет наследования стилей (каждый компонент стилизуется отдельно)
  • Единицы без px — просто числа (density-independent pixels)
  • Flexbox: flexDirection по умолчанию 'column', не 'row'
  • Нет float, нет grid, ограниченный набор свойств
  • Platform-специфичный код

    import { Platform, StyleSheet } from 'react-native'
    
    // Способ 1: Platform.OS
    const greeting = Platform.OS === 'ios' ? 'Привет, iOS!' : 'Привет, Android!'
    
    // Способ 2: Platform.select()
    const styles = StyleSheet.create({
      container: {
        ...Platform.select({
          ios: { shadowColor: '#000', shadowOffset: { width: 0, height: 2 } },
          android: { elevation: 4 },
        }),
        borderRadius: 8,
      }
    })
    
    // Способ 3: файлы с расширением
    // Button.ios.tsx    — загружается на iOS
    // Button.android.tsx — загружается на Android

    Expo vs Bare React Native

    Expo (рекомендуется для начала):

  • Готовая среда разработки, не нужен Xcode/Android Studio
  • npx create-expo-app MyApp
  • Expo Go — сканируй QR, сразу видишь приложение
  • Managed workflow: Expo управляет нативным кодом
  • Ограничен в кастомных нативных модулях (но есть Expo Modules)
  • Bare React Native:

  • npx react-native init MyApp
  • Полный контроль над нативным кодом
  • Нужен Xcode для iOS, Android Studio для Android
  • Можно писать любые нативные модули на Swift/Kotlin
  • Сложнее настройка, но без ограничений
  • Expo Managed → Expo Bare → Bare React Native
       Проще              ←→              Гибче

    Навигация и экосистема

    // React Navigation — стандарт навигации
    import { NavigationContainer } from '@react-navigation/native'
    import { createStackNavigator } from '@react-navigation/stack'
    
    const Stack = createStackNavigator()
    
    function App() {
      return (
        <NavigationContainer>
          <Stack.Navigator>
            <Stack.Screen name="Home" component={HomeScreen} />
            <Stack.Screen name="Profile" component={ProfileScreen} />
          </Stack.Navigator>
        </NavigationContainer>
      )
    }
    
    // Переход между экранами
    function HomeScreen({ navigation }) {
      return (
        <TouchableOpacity onPress={() => navigation.navigate('Profile', { userId: 42 })}>
          <Text>К профилю</Text>
        </TouchableOpacity>
      )
    }

    Примеры

    Симуляция мобильного дерева компонентов React Native в ванильном JS: фабрики компонентов, StyleSheet-объект и определение платформы

    // Симулируем архитектуру React Native на ванильном JS.
    // Показываем: компонентные фабрики, StyleSheet, Platform detection.
    
    // --- StyleSheet.create симуляция ---
    
    const StyleSheet = {
      create(styles) {
        // В реальном RN здесь происходит регистрация стилей
        // и оптимизация для нативного слоя
        const result = {}
        for (const key in styles) {
          result[key] = { ...styles[key], __id: Math.random().toString(36).slice(2) }
        }
        return result
      },
      flatten(styleArray) {
        if (!Array.isArray(styleArray)) return styleArray
        return styleArray.filter(Boolean).reduce((acc, style) => ({ ...acc, ...style }), {})
      }
    }
    
    // --- Platform симуляция ---
    
    const Platform = {
      OS: 'ios',  // 'ios' | 'android' | 'web'
      select(options) {
        return options[this.OS] ?? options.default ?? {}
      }
    }
    
    // --- Базовые компоненты-фабрики ---
    
    function createComponent(type) {
      return function(props = {}) {
        const { children, style, ...rest } = props
        return {
          type,
          props: rest,
          style: StyleSheet.flatten(Array.isArray(style) ? style : [style]),
          children: Array.isArray(children) ? children : (children ? [children] : []),
        }
      }
    }
    
    const View = createComponent('View')
    const Text = createComponent('Text')
    const TouchableOpacity = createComponent('TouchableOpacity')
    const Image = createComponent('Image')
    
    // --- Стили (как StyleSheet.create) ---
    
    const styles = StyleSheet.create({
      card: {
        backgroundColor: '#fff',
        borderRadius: 12,
        padding: 16,
        margin: 8,
        ...Platform.select({
          ios: { shadowColor: '#000', shadowOpacity: 0.1, shadowRadius: 8 },
          android: { elevation: 4 },
        })
      },
      title: { fontSize: 18, fontWeight: 'bold', color: '#1a1a1a' },
      subtitle: { fontSize: 14, color: '#666', marginTop: 4 },
      button: { backgroundColor: '#007AFF', borderRadius: 8, padding: 10, marginTop: 12 },
      buttonText: { color: '#fff', textAlign: 'center', fontWeight: '600' },
    })
    
    // --- Создание мобильного компонента ---
    
    function ProfileCard(name, role) {
      const card = View({
        style: styles.card,
        children: [
          Text({ style: styles.title, children: name }),
          Text({ style: styles.subtitle, children: role }),
          TouchableOpacity({
            style: styles.button,
            onPress: () => console.log('Нажато!'),
            children: Text({ style: styles.buttonText, children: 'Подробнее' }),
          })
        ]
      })
      return card
    }
    
    // --- Рендер дерева (упрощённо) ---
    
    function renderTree(node, indent = 0) {
      const pad = '  '.repeat(indent)
      const styleKeys = node.style ? Object.keys(node.style).filter(k => k !== '__id').join(', ') : ''
      console.log(pad + '<' + node.type + (styleKeys ? ' style={' + styleKeys + '}' : '') + '>')
    
      if (typeof node.children === 'string' || typeof node.children === 'number') {
        console.log(pad + '  ' + node.children)
      } else if (Array.isArray(node.children)) {
        node.children.forEach(child => {
          if (typeof child === 'object' && child !== null) {
            renderTree(child, indent + 1)
          } else {
            console.log(pad + '  ' + child)
          }
        })
      }
    
      console.log(pad + '</' + node.type + '>')
    }
    
    // --- Запуск ---
    
    console.log('Платформа:', Platform.OS)
    console.log('')
    
    const card = ProfileCard('Алексей Иванов', 'Senior React Native Developer')
    
    console.log('=== Дерево компонентов ===')
    renderTree(card)
    
    console.log('')
    console.log('=== Стиль карточки ===')
    const cardStyleKeys = Object.keys(card.style).filter(k => k !== '__id')
    cardStyleKeys.forEach(key => console.log(' ', key + ':', card.style[key]))
    
    console.log('')
    console.log('Platform.select результат (iOS):')
    console.log(Platform.select({ ios: 'shadowColor', android: 'elevation', default: 'none' }))

    React Native: мобильная разработка

    Что такое React Native

    React Native — это фреймворк от Meta для создания нативных мобильных приложений с помощью JavaScript и React. В отличие от WebView-подходов (Cordova, PhoneGap), React Native компилирует компоненты в настоящие нативные виджеты платформы.

    Ключевые идеи:

  • Одна кодовая база для iOS и Android
  • JavaScript-поток общается с нативным через Bridge (или новая архитектура JSI)
  • Нативные компоненты — не HTML-элементы, а View, Text, Image и др.
  • Hot Reload и быстрый цикл разработки
  • Основные компоненты

    В React Native нет <div>, <span> или <p>. Вместо них — нативные аналоги:

    | Web (HTML) | React Native | Что делает |

    |---|---|---|

    | <div> | <View> | Контейнер, flexbox-блок |

    | <span>, <p> | <Text> | Любой текст |

    | <img> | <Image> | Изображение |

    | <input> | <TextInput> | Поле ввода |

    | <button> | <TouchableOpacity> | Кнопка с нажатием |

    | <ul> | <FlatList> | Прокручиваемый список |

    | <div style="overflow:scroll"> | <ScrollView> | Прокручиваемый контейнер |

    import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'
    
    function ProfileCard({ name, role, onPress }) {
      return (
        <View style={styles.card}>
          <Text style={styles.name}>{name}</Text>
          <Text style={styles.role}>{role}</Text>
          <TouchableOpacity style={styles.button} onPress={onPress}>
            <Text style={styles.buttonText}>Подробнее</Text>
          </TouchableOpacity>
        </View>
      )
    }
    
    const styles = StyleSheet.create({
      card: {
        backgroundColor: '#fff',
        borderRadius: 12,
        padding: 16,
        margin: 8,
        // React Native использует flexbox по умолчанию
        // и не поддерживает все CSS-свойства
      },
      name: { fontSize: 18, fontWeight: 'bold', color: '#1a1a1a' },
      role: { fontSize: 14, color: '#666', marginTop: 4 },
      button: { backgroundColor: '#007AFF', borderRadius: 8, padding: 10, marginTop: 12 },
      buttonText: { color: '#fff', textAlign: 'center', fontWeight: '600' },
    })

    StyleSheet: стили в React Native

    CSS в React Native — это объекты JavaScript, а не строки. StyleSheet.create() оптимизирует их и помогает с отладкой:

    // Inline стили (не рекомендуется для частых рендеров)
    <View style={{ flex: 1, backgroundColor: 'red' }} />
    
    // StyleSheet (оптимизировано)
    const styles = StyleSheet.create({
      container: {
        flex: 1,           // занять всё доступное пространство
        flexDirection: 'column',  // направление по умолчанию — column (не row!)
        alignItems: 'center',
        justifyContent: 'center',
        backgroundColor: '#f5f5f5',
      }
    })
    
    // Несколько стилей
    <View style={[styles.container, isActive && styles.active]} />

    Отличия от CSS:

  • Нет наследования стилей (каждый компонент стилизуется отдельно)
  • Единицы без px — просто числа (density-independent pixels)
  • Flexbox: flexDirection по умолчанию 'column', не 'row'
  • Нет float, нет grid, ограниченный набор свойств
  • Platform-специфичный код

    import { Platform, StyleSheet } from 'react-native'
    
    // Способ 1: Platform.OS
    const greeting = Platform.OS === 'ios' ? 'Привет, iOS!' : 'Привет, Android!'
    
    // Способ 2: Platform.select()
    const styles = StyleSheet.create({
      container: {
        ...Platform.select({
          ios: { shadowColor: '#000', shadowOffset: { width: 0, height: 2 } },
          android: { elevation: 4 },
        }),
        borderRadius: 8,
      }
    })
    
    // Способ 3: файлы с расширением
    // Button.ios.tsx    — загружается на iOS
    // Button.android.tsx — загружается на Android

    Expo vs Bare React Native

    Expo (рекомендуется для начала):

  • Готовая среда разработки, не нужен Xcode/Android Studio
  • npx create-expo-app MyApp
  • Expo Go — сканируй QR, сразу видишь приложение
  • Managed workflow: Expo управляет нативным кодом
  • Ограничен в кастомных нативных модулях (но есть Expo Modules)
  • Bare React Native:

  • npx react-native init MyApp
  • Полный контроль над нативным кодом
  • Нужен Xcode для iOS, Android Studio для Android
  • Можно писать любые нативные модули на Swift/Kotlin
  • Сложнее настройка, но без ограничений
  • Expo Managed → Expo Bare → Bare React Native
       Проще              ←→              Гибче

    Навигация и экосистема

    // React Navigation — стандарт навигации
    import { NavigationContainer } from '@react-navigation/native'
    import { createStackNavigator } from '@react-navigation/stack'
    
    const Stack = createStackNavigator()
    
    function App() {
      return (
        <NavigationContainer>
          <Stack.Navigator>
            <Stack.Screen name="Home" component={HomeScreen} />
            <Stack.Screen name="Profile" component={ProfileScreen} />
          </Stack.Navigator>
        </NavigationContainer>
      )
    }
    
    // Переход между экранами
    function HomeScreen({ navigation }) {
      return (
        <TouchableOpacity onPress={() => navigation.navigate('Profile', { userId: 42 })}>
          <Text>К профилю</Text>
        </TouchableOpacity>
      )
    }

    Примеры

    Симуляция мобильного дерева компонентов React Native в ванильном JS: фабрики компонентов, StyleSheet-объект и определение платформы

    // Симулируем архитектуру React Native на ванильном JS.
    // Показываем: компонентные фабрики, StyleSheet, Platform detection.
    
    // --- StyleSheet.create симуляция ---
    
    const StyleSheet = {
      create(styles) {
        // В реальном RN здесь происходит регистрация стилей
        // и оптимизация для нативного слоя
        const result = {}
        for (const key in styles) {
          result[key] = { ...styles[key], __id: Math.random().toString(36).slice(2) }
        }
        return result
      },
      flatten(styleArray) {
        if (!Array.isArray(styleArray)) return styleArray
        return styleArray.filter(Boolean).reduce((acc, style) => ({ ...acc, ...style }), {})
      }
    }
    
    // --- Platform симуляция ---
    
    const Platform = {
      OS: 'ios',  // 'ios' | 'android' | 'web'
      select(options) {
        return options[this.OS] ?? options.default ?? {}
      }
    }
    
    // --- Базовые компоненты-фабрики ---
    
    function createComponent(type) {
      return function(props = {}) {
        const { children, style, ...rest } = props
        return {
          type,
          props: rest,
          style: StyleSheet.flatten(Array.isArray(style) ? style : [style]),
          children: Array.isArray(children) ? children : (children ? [children] : []),
        }
      }
    }
    
    const View = createComponent('View')
    const Text = createComponent('Text')
    const TouchableOpacity = createComponent('TouchableOpacity')
    const Image = createComponent('Image')
    
    // --- Стили (как StyleSheet.create) ---
    
    const styles = StyleSheet.create({
      card: {
        backgroundColor: '#fff',
        borderRadius: 12,
        padding: 16,
        margin: 8,
        ...Platform.select({
          ios: { shadowColor: '#000', shadowOpacity: 0.1, shadowRadius: 8 },
          android: { elevation: 4 },
        })
      },
      title: { fontSize: 18, fontWeight: 'bold', color: '#1a1a1a' },
      subtitle: { fontSize: 14, color: '#666', marginTop: 4 },
      button: { backgroundColor: '#007AFF', borderRadius: 8, padding: 10, marginTop: 12 },
      buttonText: { color: '#fff', textAlign: 'center', fontWeight: '600' },
    })
    
    // --- Создание мобильного компонента ---
    
    function ProfileCard(name, role) {
      const card = View({
        style: styles.card,
        children: [
          Text({ style: styles.title, children: name }),
          Text({ style: styles.subtitle, children: role }),
          TouchableOpacity({
            style: styles.button,
            onPress: () => console.log('Нажато!'),
            children: Text({ style: styles.buttonText, children: 'Подробнее' }),
          })
        ]
      })
      return card
    }
    
    // --- Рендер дерева (упрощённо) ---
    
    function renderTree(node, indent = 0) {
      const pad = '  '.repeat(indent)
      const styleKeys = node.style ? Object.keys(node.style).filter(k => k !== '__id').join(', ') : ''
      console.log(pad + '<' + node.type + (styleKeys ? ' style={' + styleKeys + '}' : '') + '>')
    
      if (typeof node.children === 'string' || typeof node.children === 'number') {
        console.log(pad + '  ' + node.children)
      } else if (Array.isArray(node.children)) {
        node.children.forEach(child => {
          if (typeof child === 'object' && child !== null) {
            renderTree(child, indent + 1)
          } else {
            console.log(pad + '  ' + child)
          }
        })
      }
    
      console.log(pad + '</' + node.type + '>')
    }
    
    // --- Запуск ---
    
    console.log('Платформа:', Platform.OS)
    console.log('')
    
    const card = ProfileCard('Алексей Иванов', 'Senior React Native Developer')
    
    console.log('=== Дерево компонентов ===')
    renderTree(card)
    
    console.log('')
    console.log('=== Стиль карточки ===')
    const cardStyleKeys = Object.keys(card.style).filter(k => k !== '__id')
    cardStyleKeys.forEach(key => console.log(' ', key + ':', card.style[key]))
    
    console.log('')
    console.log('Platform.select результат (iOS):')
    console.log(Platform.select({ ios: 'shadowColor', android: 'elevation', default: 'none' }))

    Задание

    Создай React-компонент ProfileCard, который демонстрирует структуру React Native компонентов. Компонент принимает пропсы name, role и onPress. Используй симуляцию нативных компонентов: View как контейнер с flexbox, Text для текста, TouchableOpacity для кнопки. Стили задаются через объект styles (как StyleSheet.create в React Native).

    Подсказка

    ProfileCard должен использовать View с styles.card, два Text для name (styles.name) и role (styles.role), и TouchableOpacity с onPress для кнопки. Внутри кнопки — Text со стилем buttonText.

    Загружаем среду выполнения...
    Загружаем AI-помощника...