Написать пост

Пишем приложение со списком дел при помощи React Hooks

Аватар Сергей Ринг

React Hooks — это функции, которые позволяют определять категорию состояния и жизненный цикл (state, lifecycle) React-компонента без использования ES6-классов.

Некоторые преимущества React Hooks:

  • изолированная логика упрощает последующие тесты;
  • при распределении логики не понадобится использование render props или «компонентов высшего порядка»;
  • разделение функциональности приложения основано на логике, а не на жизненном цикле;
  • React Hooks — достойная замена ES6-классов, с которыми порой возникают проблемы даже у опытных программистов.

Для демонстрации возможностей React Hooks построим приложение, в котором можно добавлять и удалять задачи (ToDo-приложение).

Это приложение будет выполнять следующие функции:

  • отображать ваши текущие задачи;
  • позволять добавлять новые задачи через поле ввода;
  • удалять задачи.

Установка

Весь код доступен на GitHub и CodeSandbox.

			git clone https://github.com/yazeedb/react-hooks-todo
cd react-hooks-todo
npm install
		

Ветка master — уже готовый проект. Если вы хотите создавать приложение поэтапно, следуя статье, используйте ветку start.

			git checkout start
		

Для запуска проекта используйте следующую команду:

			npm start
		

Приложение должно запуститься на localhost:3000. Вы увидите пустой начальный интерфейс с названием приложения. Весь дизайн уже настроен при помощи библиотеки material-ui, так что можно сразу добавлять функциональность.

Todo-форма

Создайте новый файл src/TodoForm.js и добавьте в него следующий код:

			import React from 'react';
import TextField from '@material-ui/core/TextField';
const TodoForm = ({ saveTodo }) => {
  return (
    <form>
      <TextField
        variant="outlined"
        placeholder="Add todo"
        margin="normal"
      />
   </form>
  );
};


export default TodoForm;
		

Как можно понять из названия формы, здесь главная задача — добавить что-либо в состояние React-компонента. Вот и первый hook.

useState

Теперь давайте рассмотрим такой код:

			import { useState } from 'react';
const [value, setValue] = useState('');
		

Это функция, которая принимает начальное состояние React-компонента и возвращает массив. Вы можете использовать console.log, чтобы посмотреть, что именно она возвращает.

Под первым индексом массива находится текущее значение состояния компонента, а во второй ячейке находится обновляющая функция. Они названы value и setValue, следуя ES6 destructuring assignment.

useState с формами

Ваша форма должна отслеживать значение, вводимое пользователем и вызывать метод saveTodo() при отправке формы. useState поможет вам с этим.

Добавьте изменения в TodoForm.js, они выделены жирным шрифтом:

			import React,
		

В index.js импортируйте форму:

			...
import TodoForm from './TodoForm';
...
const App = () => {
  return (
    <div className="App">
    <Typography component="h1" variant="h2">
      Todos
    </Typography>
		
Пишем приложение со списком дел при помощи React Hooks 2

Теперь введённое значение логируется при подтверждении (нажатии на Enter).

useState с Todos

Вам также нужно состояние компонента. Импортируйте useState в index.js. Начальное состояние компонента должно быть пустым массивом.

			import React, { useState } from 'react';
...
const App = () => {
  const [todos, setTodos] = useState([]);
  return ...
		

Компонент TodoList

Для начала создайте новый файл src/TodoList.js.

Большая часть кода — это компоненты из Material-UI. Самые важные части выделены жирным шрифтом.

			import React from 'react';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
import ListItemText from '@material-ui/core/ListItemText';
import Checkbox from '@material-ui/core/Checkbox';
import IconButton from '@material-ui/core/IconButton';
import DeleteIcon from '@material-ui/icons/Delete';

const TodoList = ({
		

Чтобы всё работало корректно, требуется:

  • todos — массив всех ваших задач. При помощи метода map() вы сопоставляете каждую из них и создаёте элемент списка;
  • deleteTodo() — функция вызывается при нажатии на IconButton и передает индекс, который однозначно идентифицирует пункт списка.

Импортируйте этот компонент в ваш файл index.js.

			...
import TodoList from './TodoList';
import './styles.css';
const App = () => { ...
		

И используйте в App-функции:

			...
<TodoForm saveTodo={console.warn} />
<TodoList todos={todos} />
		

Добавление новых задач

В index.js добавляем метод SaveTodo()в todo-форму.

			<TodoForm
  saveTodo={todoText => {
    const trimmedText = todoText.trim();
    if (trimmedText.length > 0) {
      setTodos([...todos, trimmedText]);
    }
  }}
/>
		

Проще всего объединить уже существующие задачи с новой. Дополнительные пробелы будут вырезаны.

Очистка поля ввода

Заметьте, что на данном этапе после добавления новой задачи поле ввода не очищено, и это не очень-то хорошо. Исправить это можно внеся маленькое изменение в TodoForm.js.

			<form
  onSubmit={event => {
    event.preventDefault();
    saveTodo(value);
		

Теперь, как только todo будет сохранён, состояние формы преобразуется в пустую строку:

Удаление задач

Так как TodoList содержит индексы всех задач, можно без проблем найти нужный элемент для удаления.

			// TodoList.js
<IconButton
  aria-label="Delete"
  onClick={() => {
    deleteTodo(index);
  }}
>
  <DeleteIcon />
</IconButton>
		

Теперь примените в index.js.

			<TodoList
  todos={todos}
		

Если задача с соответствующим index не найдена, остальные задачи останутся в состоянии формы благодаря использованию setTodos().

Извлечение useState из компонента Todos

В начале статьи упоминалось, что React Hooks хорошо подходят для разделения состояния и логики компонента. И вот как это могло бы выглядеть в вашем приложении.

Создайте новый файл src/useTodoState.js.

			import { useState } from 'react';
export default initialValue => {
  const [todos, setTodos] = useState(initialValue);

  return {
    todos,
    addTodo: todoText => {
      setTodos([...todos, todoText]);
    },
    deleteTodo: todoIndex => {
      const newTodos = todos
        .filter((_, index) => index !== todoIndex);

      setTodos(newTodos);
    }
  };
};
		

По сути это тот же самый код, что и в index.js, но теперь управление состоянием не так плотно связано с компонентом.

Осталось только всё импортировать. Новый код выделен жирным шрифтом.

			import React from 'react';
import ReactDOM from 'react-dom';
import Typography from '@material-ui/core/Typography';
import TodoForm from './TodoForm';
import TodoList from './TodoList';
		

Абстракция useState в форме ввода

То же самое можно сделать с формой.

Создайте новый файл src/useInputState.js.

			import { useState } from 'react';
export default initialValue => {
  const [value, setValue] = useState(initialValue);

  return {
    value,
    onChange: event => {
      setValue(event.target.value);
    },
    reset: () => setValue('')
  };
};
		

Сейчас TodoForm.js должен выглядеть так:

			import React from 'react';
import TextField from '@material-ui/core/TextField';
import useInputState from './useInputState';
const TodoForm = ({ saveTodo }) => {
  const { value, reset, onChange } = useInputState('');

  return (
    <form
      onSubmit={event => {
        event.preventDefault();

        saveTodo(value);
        reset();
      }}
    >
      <TextField
        variant="outlined"
        placeholder="Add todo"
        margin="normal"
        onChange={onChange}
        value={value}
      />
    </form>
  );
};

export default TodoForm;
		

В данной статье мы рассмотрели создание ToDo-приложения с помощью React Hooks. Если вы любите React и хотите узнать о нём больше, обратите внимание на нашу статью:

 

Следите за новыми постами
Следите за новыми постами по любимым темам
36К открытий36К показов