Лекция 4. Компоненты, хуки и стилизация в React Native
Введение
В предыдущих лекциях мы познакомились с экосистемой Expo и React Native, развернули первый проект и запустили его в Expo Go. Теперь пора разобраться, из чего именно строится интерфейс мобильного приложения и как им управлять.
Эта лекция посвящена трём фундаментальным темам:
- Система компонентов React Native — встроенные «кирпичики» интерфейса.
- Хуки (
useState,useEffect) — управление состоянием и жизненным циклом. - Стилизация —
StyleSheetи компоновка через Flexbox.
В конце мы обзорно посмотрим, как из компонентов обращаться к сенсорам устройства (камера, геолокация, уведомления) через модули Expo.
1. Система компонентов React Native
1.1. Чем RN отличается от веб-React
Если вы писали на React для веба, то возвращали из компонентов HTML-теги:
<div>, <span>, <img>, <input>. В React Native HTML-тегов и DOM нет.
React Native работает не с браузером, а с нативными примитивами платформы:
<div>в вебе →<View>в RN → нативныйUIView(iOS) /android.view.View(Android);<span>/<p>→<Text>→ нативныйUILabel/TextView;<img>→<Image>;<input>→<TextInput>.
То есть JSX остаётся, но «теги» — это JS-компоненты, которые нужно
импортировать из react-native. Браузерных элементов здесь не существует.
// Это НЕ сработает в React Native:// return <div>Привет</div>;
// Правильно:import { View, Text } from 'react-native';
function Hello() { return ( <View> <Text>Привет, React Native!</Text> </View> );}Важное правило: любой текст должен находиться внутри
<Text>. Нельзя писать голую строку прямо в<View>— это вызовет ошибку.
1.2. Основные встроенные компоненты
| Компонент | Назначение |
|---|---|
View | Базовый контейнер (аналог div), основа компоновки |
Text | Отображение текста |
Image | Картинки (локальные и по URL) |
ScrollView | Прокручиваемая область для небольшого объёма контента |
FlatList | Эффективный список для длинных/динамических данных |
TextInput | Поле ввода текста |
Pressable | Универсальная нажимаемая область |
Button | Простая платформенная кнопка |
1.3. View, Text, Image
import { View, Text, Image } from 'react-native';
function Card() { return ( <View style={{ alignItems: 'center' }}> <Image style={{ width: 64, height: 64 }} source={{ uri: 'https://reactnative.dev/img/tiny_logo.png' }} /> <Text>React Native</Text> </View> );}- Для удалённой картинки в
sourceпередаётся объект{ uri: '...' }. - Для локальной —
source={require('./assets/logo.png')}.
1.4. ScrollView и FlatList
ScrollView рендерит все дочерние элементы сразу — подходит для небольшого
количества контента. FlatList рендерит только видимые элементы («виртуализация»),
поэтому именно его используют для длинных списков.
import { FlatList, Text, View } from 'react-native';
const data = [ { id: '1', title: 'Первый' }, { id: '2', title: 'Второй' }, { id: '3', title: 'Третий' },];
function List() { return ( <FlatList data={data} keyExtractor={(item) => item.id} renderItem={({ item }) => ( <View style={{ padding: 12 }}> <Text>{item.title}</Text> </View> )} /> );}data— массив данных;renderItem— функция отрисовки одного элемента;keyExtractor— уникальный ключ (нужен для оптимизации).
1.5. Pressable и Button
Button прост, но почти не настраивается по виду. Pressable — гибкая
нажимаемая обёртка, позволяющая реагировать на нажатие и менять стиль.
import { Pressable, Text } from 'react-native';
function MyButton({ onPress }) { return ( <Pressable onPress={onPress} style={({ pressed }) => ({ backgroundColor: pressed ? '#1565c0' : '#1976d2', padding: 12, borderRadius: 8, })} > <Text style={{ color: 'white', textAlign: 'center' }}>Нажми меня</Text> </Pressable> );}2. JSX, пропсы и функциональные компоненты
2.1. Функциональные компоненты
Современный React — это функции, возвращающие JSX. Имя компонента всегда пишется с заглавной буквы.
function Greeting() { return <Text>Привет!</Text>;}2.2. Пропсы (props)
Пропсы — это «входные параметры» компонента, доступные только для чтения. Через них родитель передаёт данные дочернему компоненту.
function Greeting({ name }) { return <Text>Привет, {name}!</Text>;}
// Использование:// <Greeting name="Шамиль" />// <Greeting name="Анна" />- В JSX выражения JavaScript вставляются в фигурных скобках
{ }. - Пропсы менять внутри компонента нельзя — они неизменяемы. Изменяемые данные хранятся в состоянии (см. дальше).
3. Хуки: useState и useEffect
3.1. Зачем нужно состояние
Состояние (state) — это данные, которые могут меняться во время работы компонента, и при изменении которых интерфейс автоматически перерисовывается.
Хук useState возвращает массив из двух элементов: текущее значение и функцию
для его обновления.
import { useState } from 'react';import { View, Text, Button } from 'react-native';
export default function Counter() { const [count, setCount] = useState(0); // начальное значение 0
return ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Text>Счётчик: {count}</Text> <Button title="Увеличить" onPress={() => setCount(count + 1)} /> </View> );}Как это работает:
useState(0)создаёт состояниеcountсо значением0.- При нажатии вызывается
setCount(count + 1). - React запоминает новое значение и перерисовывает компонент.
- На экране появляется обновлённое число.
Менять состояние напрямую (
count = count + 1) нельзя — перерисовки не будет. Всегда используем функцию-сеттер (setCount).
3.2. Ввод текста и управляемые поля
TextInput обычно делают «управляемым»: его значение хранится в состоянии.
import { useState } from 'react';import { View, Text, TextInput } from 'react-native';
export default function NameForm() { const [name, setName] = useState('');
return ( <View style={{ padding: 20 }}> <TextInput placeholder="Введите имя" value={name} onChangeText={setName} style={{ borderWidth: 1, borderColor: '#ccc', padding: 8, borderRadius: 6 }} /> <Text style={{ marginTop: 10 }}> {name ? `Привет, ${name}!` : 'Поле пустое'} </Text> </View> );}valueсвязывает поле с состоянием;onChangeTextобновляет состояние при каждом вводе символа.
3.3. Кратко про useEffect
Хук useEffect выполняет побочные эффекты: загрузку данных, подписки,
таймеры, обращение к API устройства. Он запускается после отрисовки.
import { useState, useEffect } from 'react';import { Text } from 'react-native';
export default function Clock() { const [time, setTime] = useState(new Date().toLocaleTimeString());
useEffect(() => { const id = setInterval(() => { setTime(new Date().toLocaleTimeString()); }, 1000);
return () => clearInterval(id); // очистка при размонтировании }, []); // [] — выполнить один раз при монтировании
return <Text>Время: {time}</Text>;}- Второй аргумент — массив зависимостей:
[]— эффект сработает один раз при появлении компонента;[x]— будет срабатывать при каждом измененииx;- отсутствует — при каждой перерисовке.
- Функция, которую возвращает эффект, — это очистка (отписка, остановка таймера).
4. Стилизация интерфейса
4.1. Инлайн-стили и StyleSheet
Стили в RN — это объекты JavaScript (не CSS-файлы). Свойства пишутся в
camelCase: backgroundColor, fontSize, marginTop.
Инлайн-стиль задаётся прямо в пропе style:
<Text style={{ fontSize: 18, color: 'tomato' }}>Текст</Text>Лучшая практика — выносить стили в StyleSheet.create. Это улучшает читаемость
и слегка оптимизирует производительность.
import { StyleSheet, View, Text } from 'react-native';
function Box() { return ( <View style={styles.container}> <Text style={styles.title}>Заголовок</Text> </View> );}
const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#f5f5f5', }, title: { fontSize: 20, fontWeight: 'bold', color: '#333', },});Стили можно комбинировать массивом — последующий перекрывает предыдущий:
<Text style={[styles.title, { color: 'red' }]}>Красный заголовок</Text>4.2. Особенности стилей в RN
- Нет классов CSS, селекторов, каскада и наследования (кроме части свойств
Text). - Размеры по умолчанию без единиц (это плотностно-независимые точки — dp),
проценты задаются строкой:
width: '50%'. - Главный инструмент компоновки — Flexbox.
5. Flexbox в React Native
Flexbox — это система раскладки, отвечающая на вопрос «как расположить дочерние
элементы внутри контейнера». В RN он включён у каждого View по умолчанию.
5.1. Главное отличие от веба
В вебе flex-direction по умолчанию row. В React Native по умолчанию
flexDirection: 'column' — элементы идут сверху вниз. Это логично для
вертикальных мобильных экранов.
5.2. Оси
- Главная ось (main axis) — задаётся
flexDirection.column(по умолчанию) → главная ось вертикальна;row→ главная ось горизонтальна.
- Поперечная ось (cross axis) — перпендикулярна главной.
justifyContent выравнивает по главной оси, alignItems — по поперечной.
5.3. Таблица основных свойств Flexbox
| Свойство | Значения | Что делает |
|---|---|---|
flexDirection | column (по умолч.), row, column-reverse, row-reverse | Направление главной оси |
justifyContent | flex-start, center, flex-end, space-between, space-around, space-evenly | Выравнивание по главной оси |
alignItems | stretch (по умолч.), flex-start, center, flex-end, baseline | Выравнивание по поперечной оси |
alignSelf | те же, что у alignItems | Переопределяет выравнивание для одного элемента |
flex | число (напр. 1, 2) | Доля свободного пространства, занимаемая элементом |
flexWrap | nowrap (по умолч.), wrap | Перенос элементов на новую строку |
gap | число | Отступ между дочерними элементами |
5.4. Пример: центрирование
const styles = StyleSheet.create({ container: { flex: 1, // занять весь экран justifyContent: 'center', // по вертикали (главная ось — column) alignItems: 'center', // по горизонтали (поперечная ось) },});5.5. Пример: горизонтальный ряд
function Row() { return ( <View style={{ flexDirection: 'row', justifyContent: 'space-between', padding: 16 }}> <Text>A</Text> <Text>B</Text> <Text>C</Text> </View> );}5.6. Адаптивные размеры через flex
Свойство flex распределяет пространство пропорционально. Ниже три блока делят
ширину как 1 : 2 : 1.
import { View } from 'react-native';
function Adaptive() { return ( <View style={{ flex: 1, flexDirection: 'row' }}> <View style={{ flex: 1, backgroundColor: '#ef9a9a' }} /> <View style={{ flex: 2, backgroundColor: '#a5d6a7' }} /> <View style={{ flex: 1, backgroundColor: '#90caf9' }} /> </View> );}Такой подход адаптируется под любой размер экрана без жёстко заданных пикселей.
Для значений, зависящих от размеров экрана, также используют API Dimensions или
хук useWindowDimensions.
6. Доступ к сенсорам и сервисам устройства
Через модули Expo SDK компоненты могут обращаться к возможностям телефона.
Каждый модуль устанавливается командой npx expo install <имя>.
Типовой сценарий: запросить разрешение → выполнить действие.
6.1. Геолокация (expo-location)
import * as Location from 'expo-location';
async function getLocation() { const { status } = await Location.requestForegroundPermissionsAsync(); if (status !== 'granted') { console.log('Нет доступа к геолокации'); return; } const location = await Location.getCurrentPositionAsync({}); console.log(location.coords.latitude, location.coords.longitude);}6.2. Камера (expo-camera)
import { CameraView, useCameraPermissions } from 'expo-camera';import { Button } from 'react-native';
function Scanner() { const [permission, requestPermission] = useCameraPermissions();
if (!permission?.granted) { return <Button title="Разрешить камеру" onPress={requestPermission} />; } return <CameraView style={{ flex: 1 }} />;}6.3. Уведомления (expo-notifications)
import * as Notifications from 'expo-notifications';
async function notify() { await Notifications.requestPermissionsAsync(); await Notifications.scheduleNotificationAsync({ content: { title: 'Привет!', body: 'Это локальное уведомление' }, trigger: { seconds: 5 }, });}Другие популярные модули:
expo-sensors(акселерометр, гироскоп),expo-image-picker(выбор фото),expo-haptics(вибрация).
Краткие итоги
- В React Native нет DOM и HTML-тегов: интерфейс строится из нативных
компонентов (
View,Text,Image,ScrollView,FlatList,TextInput,Pressable,Button), которые импортируются изreact-native. - Весь текст обязан находиться внутри
<Text>. - Пропсы — неизменяемые входные параметры; состояние (
useState) — изменяемые данные, при обновлении которых компонент перерисовывается. useEffectвыполняет побочные эффекты (таймеры, загрузку данных, доступ к устройству) и умеет очищать ресурсы.- Стили задаются объектами JS (camelCase) через инлайн-
styleилиStyleSheet.create. Каскада и классов CSS нет. - Главный инструмент раскладки — Flexbox; в отличие от веба,
flexDirectionпо умолчаниюcolumn.justifyContent— по главной оси,alignItems— по поперечной,flex— для адаптивных размеров. - Доступ к сенсорам идёт через модули Expo по схеме «запрос разрешения → действие».
Вопросы для самопроверки
- Чем встроенные компоненты React Native (
View,Text) отличаются от HTML-элементов веб-React? Почему в RN нет DOM? - Как работает хук
useState? Что произойдёт при попытке изменить состояние напрямую, минуя функцию-сеттер? Для чего служитuseEffect? - Чем
StyleSheet.createотличается от инлайн-стилей? Назовите особенности системы стилей RN по сравнению с CSS. - Как устроен Flexbox в React Native? В чём отличие значения
flexDirectionпо умолчанию от веба, и за что отвечаютjustifyContent,alignItemsиflex?