Перейти к содержимому

Лекция 4. Компоненты, хуки и стилизация в React Native

Введение

В предыдущих лекциях мы познакомились с экосистемой Expo и React Native, развернули первый проект и запустили его в Expo Go. Теперь пора разобраться, из чего именно строится интерфейс мобильного приложения и как им управлять.

Эта лекция посвящена трём фундаментальным темам:

  1. Система компонентов React Native — встроенные «кирпичики» интерфейса.
  2. Хуки (useState, useEffect) — управление состоянием и жизненным циклом.
  3. Стилизация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>
);
}

Как это работает:

  1. useState(0) создаёт состояние count со значением 0.
  2. При нажатии вызывается setCount(count + 1).
  3. React запоминает новое значение и перерисовывает компонент.
  4. На экране появляется обновлённое число.

Менять состояние напрямую (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

СвойствоЗначенияЧто делает
flexDirectioncolumn (по умолч.), row, column-reverse, row-reverseНаправление главной оси
justifyContentflex-start, center, flex-end, space-between, space-around, space-evenlyВыравнивание по главной оси
alignItemsstretch (по умолч.), flex-start, center, flex-end, baselineВыравнивание по поперечной оси
alignSelfте же, что у alignItemsПереопределяет выравнивание для одного элемента
flexчисло (напр. 1, 2)Доля свободного пространства, занимаемая элементом
flexWrapnowrap (по умолч.), 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 по схеме «запрос разрешения → действие».

Вопросы для самопроверки

  1. Чем встроенные компоненты React Native (View, Text) отличаются от HTML-элементов веб-React? Почему в RN нет DOM?
  2. Как работает хук useState? Что произойдёт при попытке изменить состояние напрямую, минуя функцию-сеттер? Для чего служит useEffect?
  3. Чем StyleSheet.create отличается от инлайн-стилей? Назовите особенности системы стилей RN по сравнению с CSS.
  4. Как устроен Flexbox в React Native? В чём отличие значения flexDirection по умолчанию от веба, и за что отвечают justifyContent, alignItems и flex?