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

Практика 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 %.


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

  1. Почему задачу нельзя добавлять через tasks.push(...) напрямую и что произойдёт с интерфейсом в этом случае?
  2. За что отвечают пропсы value и onChangeText у TextInput?
  3. Зачем FlatList нужен keyExtractor и чем FlatList лучше ScrollView для длинного списка?
  4. Какие свойства Flexbox выровняли поле ввода и кнопку в одну строку и почему у поля стоит flex: 1?
  5. Что нужно изменить, чтобы передавать в TaskItem дополнительные данные (например, статус «выполнено»)?

Ресурсы