Практика 4. Практическая работа 4. Компоненты, состояние и Flexbox
Раздел 1. Привязка к лекции 4 (компоненты React Native, хуки, стилизация, Flexbox). Работа практико-кодовая: вы собираете рабочий экран приложения.
Цели работы
- Научиться собирать экран из встроенных компонентов React Native
(
View,Text,TextInput,FlatList,Pressable). - Освоить управление изменяемыми данными через хук
useState. - Применить стилизацию через
StyleSheet.createи компоновку через Flexbox. - Вынести повторяющийся элемент списка в отдельный компонент с пропсами.
Итог работы — экран «Список задач»: заголовок, поле ввода, кнопка добавления, прокручиваемый список задач и счётчик их количества.
Коротко о теории
Опорные моменты из лекции 4, которые понадобятся:
- Текст только в
<Text>. Голая строка прямо в<View>вызовет ошибку. useStateвозвращает пару[значение, сеттер]. Менять состояние напрямую нельзя — только через сеттер, иначе перерисовки не будет.- Управляемый
TextInput:valueсвязан с состоянием,onChangeTextобновляет его при каждом вводе символа. FlatListрендерит только видимые элементы и требуетdata,renderItemиkeyExtractor(уникальный ключ).- Стили — это объекты JS в camelCase; выносим их в
StyleSheet.create. - Flexbox: по умолчанию
flexDirection: 'column'.justifyContent— по главной оси,alignItems— по поперечной,flex— для адаптивных размеров.
Задание
Предполагается готовый проект Expo (см. практику 1). Работаем в App.js.
Шаг 1. Каркас экрана и импорты
Создайте основной компонент и подключите нужные модули. Текст уже оборачиваем
в <Text>, а корневой View растягиваем на весь экран через flex: 1.
import { useState } from 'react';import { StyleSheet, View, Text, TextInput, Pressable, FlatList,} from 'react-native';
export default function App() { return ( <View style={styles.container}> <Text style={styles.title}>Список задач</Text> </View> );}Шаг 2. Состояние через useState
Заведите два состояния: текст в поле ввода (text) и массив задач (tasks).
Каждая задача — объект с уникальным id и текстом title.
const [text, setText] = useState('');const [tasks, setTasks] = useState([]);Шаг 3. Поле ввода и кнопка добавления
Свяжите TextInput с состоянием (value + onChangeText) и добавьте кнопку.
Поле и кнопка стоят в одной строке — поэтому контейнер с flexDirection: 'row'.
<View style={styles.inputRow}> <TextInput style={styles.input} placeholder="Новая задача" value={text} onChangeText={setText} /> <Pressable style={styles.addButton} onPress={addTask}> <Text style={styles.addButtonText}>+</Text> </Pressable></View>Шаг 4. Обработчик добавления
Функция addTask проверяет, что поле не пустое, добавляет новый элемент в массив
через сеттер (не мутируя старый массив) и очищает поле ввода.
function addTask() { const value = text.trim(); if (!value) return; // пустые задачи не добавляем
const newTask = { id: Date.now().toString(), title: value }; setTasks((prev) => [...prev, newTask]); // новый массив, без мутации setText(''); // очищаем поле}Шаг 5. Счётчик и рендер списка
Количество задач — это просто tasks.length. Список рисуем через FlatList.
<Text style={styles.counter}>Всего задач: {tasks.length}</Text>
<FlatList data={tasks} keyExtractor={(item) => item.id} contentContainerStyle={{ gap: 8 }} renderItem={({ item }) => ( <View style={styles.taskItem}> <Text style={styles.taskText}>{item.title}</Text> </View> )}/>Шаг 6. Стилизация через StyleSheet и Flexbox
Все стили выносим в StyleSheet.create. Обратите внимание на flex, gap,
flexDirection, alignItems и отступы.
const styles = StyleSheet.create({ container: { flex: 1, paddingTop: 60, paddingHorizontal: 20, backgroundColor: '#f5f5f5', }, title: { fontSize: 24, fontWeight: 'bold', marginBottom: 16, color: '#333', }, inputRow: { flexDirection: 'row', alignItems: 'center', gap: 8, marginBottom: 12, }, input: { flex: 1, // поле занимает всё свободное место borderWidth: 1, borderColor: '#ccc', borderRadius: 8, padding: 10, backgroundColor: 'white', }, addButton: { width: 44, height: 44, borderRadius: 8, backgroundColor: '#1976d2', justifyContent: 'center', alignItems: 'center', }, addButtonText: { color: 'white', fontSize: 22 }, counter: { marginBottom: 8, color: '#666' }, taskItem: { padding: 12, borderRadius: 8, backgroundColor: 'white', }, taskText: { fontSize: 16, color: '#333' },});После сохранения вы увидите рабочий экран: можно вводить задачи, добавлять их кнопкой, список прокручивается, счётчик растёт.
Дополнительное задание
Д1. Вынести элемент списка в отдельный компонент с пропсами
Создайте компонент TaskItem, который принимает через пропсы текст задачи и
функцию удаления. Это разгружает основной компонент и делает код переиспользуемым.
function TaskItem({ title, onDelete }) { return ( <View style={styles.taskItem}> <Text style={styles.taskText}>{title}</Text> <Pressable onPress={onDelete} style={styles.deleteButton}> <Text style={styles.deleteButtonText}>✕</Text> </Pressable> </View> );}Чтобы текст и кнопка удаления разошлись по краям, обновите стиль taskItem:
taskItem: { flexDirection: 'row', justifyContent: 'space-between', // текст слева, кнопка справа alignItems: 'center', padding: 12, borderRadius: 8, backgroundColor: 'white',},deleteButton: { paddingHorizontal: 8 },deleteButtonText: { color: '#e53935', fontSize: 18 },Д2. Кнопка удаления
Добавьте обработчик и используйте TaskItem внутри renderItem.
function deleteTask(id) { setTasks((prev) => prev.filter((task) => task.id !== id));}
// внутри FlatList:renderItem={({ item }) => ( <TaskItem title={item.title} onDelete={() => deleteTask(item.id)} />)}Критерии оценки
| Критерий | Вес |
|---|---|
Экран собран из компонентов RN, текст внутри <Text> | 15 % |
Состояние реализовано через useState (текст и массив задач) | 20 % |
| Добавление работает корректно (без мутаций, поле очищается) | 20 % |
Список рендерится (FlatList/map) и есть рабочий счётчик | 15 % |
Стилизация через StyleSheet и Flexbox (выравнивание, отступы) | 15 % |
Доп.: вынесен TaskItem с пропсами и работает удаление | 15 % |
Минимум для зачёта — 60 %.
Вопросы для самопроверки
- Почему задачу нельзя добавлять через
tasks.push(...)напрямую и что произойдёт с интерфейсом в этом случае? - За что отвечают пропсы
valueиonChangeTextуTextInput? - Зачем
FlatListнуженkeyExtractorи чемFlatListлучшеScrollViewдля длинного списка? - Какие свойства Flexbox выровняли поле ввода и кнопку в одну строку и почему
у поля стоит
flex: 1? - Что нужно изменить, чтобы передавать в
TaskItemдополнительные данные (например, статус «выполнено»)?
Ресурсы
- React Native — Components and APIs: https://reactnative.dev/docs/components-and-apis
- React Native — Handling Text Input: https://reactnative.dev/docs/handling-text-input
- React Native — Using a FlatList: https://reactnative.dev/docs/using-a-listview
- React Native — Layout with Flexbox: https://reactnative.dev/docs/flexbox
- React — useState: https://react.dev/reference/react/useState