Создание To-Do List в React с помощью Redux Toolkit в TypeScript

Создание To-Do List в React с помощью Redux Toolkit в TypeScript

От автора: сегодня мы узнаем, как составить список дел с помощью Redux. Redux — популярная библиотека управления состоянием, особенно в сочетании с React. Возьмем Redux Toolkit. Redux Toolkit значительно сокращает объем кода Redux, который нам придется писать, и значительно улучшает опыт разработки, как вы скоро увидите. Итак, без лишних слов, давайте погрузимся в создание нашего списка дел.

Прежде чем сделаем что-либо еще, нашим первым шагом будет создание проекта. Мы будем использовать один из шаблонов приложений create-react-app в качестве стартера для этого проекта. Выполнение следующей команды создаст стартовый проект, который мы будем использовать.

Есть шаблон для redux-typescript, но нам все равно придется удалить большую часть файлов и кода для этого проекта.

Настройка проекта

npx create-react-app redux-todo-list --template typescript

После того, как проект собран, вы можете открыть его в любом редакторе кода по вашему выбору. Я буду использовать VS Code, но вы можете использовать все, что удобно. Открыв проект, пора удалить ненужные файлы из папки src:

App.test.tsx

App.css

index.css

logo.svg

setupTests.ts

reportWebVitals.ts

Папка src должна выглядеть примерно так, когда мы закончим.

Создание To-Do List в React с помощью Redux Toolkit в TypeScript

После удаления всех ненужных файлов и папок пора очистить файлы App.tsx и index.tsx. Они должны выглядеть так, как показано ниже.

App.tsx

function App() { return ( <> <h1>Redux To-Do List App</h1> </> );
} export default App;

index.tsx

import React from "react";
import ReactDOM from "react-dom";
import App from "./App"; ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById("root")
);

Последнее, что нам нужно сделать для настройки проекта, — это установить несколько пакетов. Выполнение следующей команды установит пакеты, которые нам понадобятся для создания приложения:

npm install @reduxjs/toolkit react-redux @material-ui/core @material-ui/icons uuid @types/uuid

Или если вы используете yarn:

yarn add @reduxjs/toolkit react-redux @material-ui/core @material-ui/icons uuid @types/uuid

Описание пакетов

Мы будем использовать @ material-ui/icons и @ material-ui/core для стилизации нашего приложения, uuid для генерации уникальных идентификаторов, @ reduxjs/toolkit отвечает за управление состоянием, а response-redux будет использоваться для предоставления нашего состояния. в приложение React.

Убедитесь, что наше приложение работает

Выполните следующую команду, чтобы убедиться, что наше приложение готово к работе.

npm start

Или если вы используете yarn.

yarn start

Приступим к кодированию

Создание типа To-Do

Поскольку наш проект структурирован так, как нам нужно, пришло время написать код. Я хочу начать с создания модели того, как будет структурирован наш список дел. Начнем с создания новой папки в папке src и назовем ее models. Давайте создадим файл в папке моделей, который называется Todo.ts. В нём напишем следующее:

Todo.ts

export interface Todo { id: string; description: string; completed: boolean;
}

Настройка Redux в приложении

Теперь, когда создан интерфейс для нашего Todo, пришло время настроить Redux и то, что Redux Toolkit называет «срезом». Мы можем продолжить и создать новую папку в нашей папке src под названием redux . Затем давайте добавим файл todoSlice.ts в папку redux . Этот клас будет контролировать работу управления состоянием приложения и будет выглядеть следующим образом.

todoSlice.ts

import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { Todo } from "../models/Todo";
import { v4 as uuidv4 } from "uuid"; const initialState = [] as Todo[]; const todoSlice = createSlice({ name: "todos", initialState, reducers: { addTodo: { reducer: (state, action: PayloadAction<Todo>) => { state.push(action.payload); }, prepare: (description: string) => ({ payload: { id: uuidv4(), description, completed: false, } as Todo, }), }, removeTodo(state, action: PayloadAction<string>) { const index = state.findIndex((todo) => todo.id === action.payload); state.splice(index, 1); }, setTodoStatus( state, action: PayloadAction<{ completed: boolean; id: string }> ) { const index = state.findIndex((todo) => todo.id === action.payload.id); state[index].completed = action.payload.completed; }, },
}); export const { addTodo, removeTodo, setTodoStatus } = todoSlice.actions;
export default todoSlice.reducer;

Объяснение приведенного выше кода

Начнем с пятой строки приведенного выше примера. Это довольно очевидно, но нам нужно будет установить, какое состояние будет в начале нашего приложения (в данном случае это пустой массив типа Todo). Когда мы перейдем к строке 7, мы впервые увидим Redux Toolkit в действии.

Теперь createSlice — это функция, встроенная в Redux Toolkit, которая принимает объект в качестве параметра с 3 обязательными полями.

Первое поле — это name. Oно используется для именования сгенерированных типов действий из createSlice.

Второе поле — это initialState, и, как можно понять из названия, это будет то состояние, в котором мы изначально запускаем наше приложение.

Третье поле — это reducers, в нём будет происходить различная обработка состояния. В приведенном выше коде вы можете видеть, что у меня есть 3 редуктора (для добавления, удаления и установки состояния завершения задач). Вы также заметите, что то, как я управляю состоянием, делает его изменчивым (хотя это не так). За кулисами Redux Toolkit использует библиотеку под названием Immer. Короче говоря, Immer — это просто библиотека, которая упрощает запись обработки неизменяемого состояния (меньше шаблонов). Ниже приведена ссылка на страницу документации Redux Toolkit, на которой это описано, если вам интересно.

Приведенный выше код будет контролировать состояние всего нашего приложения. Те из вас, кто пишет весь свой код redux вручную, заметят, насколько уменьшится количество шаблонов.

Следующим шагом будет настройка хранилища для Redux. Давайте начнем с создания нового файла в папке Redux под названием store.ts.

store.ts

import { configureStore } from "@reduxjs/toolkit";
import todosReducer from "./todoSlice"; export const store = configureStore({ reducer: todosReducer,
}); export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;

Объяснение приведенного выше кода

Здесь особо не о чем говорить, потому что это очень просто, но Redux Toolkit предоставляет абстракцию в хранилище Redux под названием configureStore . По сути, все, что нам нужно сделать, чтобы всё сработало, — это передать наши фрагменты параметру reducer в функции configureStore . Кроме того, мы предоставляем типы, которые будут переданы в useSelector и useDispatch ( перехватчики response -redux) как способ описания типов. Ниже приведена ссылка на документацию API для configureStore для тех, кто хотел бы узнать больше.

Добавление Redux в наше приложение React

Для тех из вас, кто раньше использовал Redux, этот процесс остается неизменным. Здесь в игру вступает установленный нами ранее пакет react-redux. Все, что нам нужно, это обернуть приложение React внутри компонента, предоставляемого react-redux. Для этого мы собираемся отредактировать файл index.tsx.

index.tsx

import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import App from "./App";
import { store } from "./redux/store"; ReactDOM.render( <React.StrictMode> <Provider store={store}> <App /> </Provider> </React.StrictMode>, document.getElementById("root")
);

Объяснение приведенного выше кода

Здесь тоже нечего объяснять, но, по сути, мы оборачиваем компонент Provider вокруг компонента App. Это позволит нам подключиться к хранилищу Redux из любой точки приложения React, как вы сейчас увидите. Ниже приведена ссылка на документацию response-redux по компоненту Provider, если вы хотите узнать о нем больше.

Интеграция Redux с пользовательским интерфейсом

Это будет последний шаг, и вы увидите, насколько просто Redux Toolkit позволит нам работать с react-redux.
Вот краткое объяснение того, что мы собираемся делать, прежде чем мы начнем. Прежде всего, мы не будем писать собственные стили для этого приложения (хотя вы можете, если хотите). Здесь в игру вступит Material-UI. У нас будет текстовое поле и кнопка, которая будет отвечать за добавление элементов в наш список дел. Под этим текстовым полем и кнопкой будет наш список элементов. Каждый элемент будет отображаться с описанием элементов задачи, флажком для отметки элемента как выполненным и кнопкой удаления (соответственно — для удаления задач из списка). Вот и все, поэтому давайте напишем код.

index.tsx

import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import App from "./App";
import { store } from "./redux/store";
import CssBaseline from "@material-ui/core/CssBaseline"; ReactDOM.render( <React.StrictMode> <CssBaseline /> <Provider store={store}> <App /> </Provider> </React.StrictMode>, document.getElementById("root")
);

Объяснение приведенного выше кода

Первое, что нам нужно сделать, это добавить несколько строк кода в файл index.tsx . Мы собираемся импортировать и добавлять компонент CSSBaseline из Material-UI. Все, что он делает, — это добавление некоторых интеллектуальных сбросов и значений по умолчанию в наш CSS.

Пришло время написать пользовательский интерфейс. Весь код в моем примере будет находиться в файле App.tsx, но я настоятельно рекомендую разбить все на отдельные файлы и компоненты, если вы работаете над реальным приложением.

App.tsx

// React App
import { useState } from "react";
// Material-UI Imports
import Container from "@material-ui/core/Container";
import Typography from "@material-ui/core/Typography";
import Button from "@material-ui/core/Button";
import TextField from "@material-ui/core/TextField";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
import ListItemText from "@material-ui/core/ListItemText";
import ListItemSecondaryAction from "@material-ui/core/ListItemSecondaryAction";
import Checkbox from "@material-ui/core/Checkbox";
import IconButton from "@material-ui/core/IconButton";
import DeleteIcon from "@material-ui/icons/Delete";
// Other Imports
import { useDispatch, useSelector } from "react-redux";
import { AppDispatch, RootState } from "./redux/store";
import { addTodo, removeTodo, setTodoStatus } from "./redux/todoSlice"; function App() { //React Hooks const [todoDescription, setTodoDescription] = useState(""); //React Redux Hooks const todoList = useSelector((state: RootState) => state); const dispatch = useDispatch<AppDispatch>(); //Rendering return ( <Container maxWidth="xs"> <Typography style={{ textAlign: "center" }} variant="h3"> Redux List App </Typography> <TextField variant="outlined" label="To Do Item" fullWidth onChange={(e) => setTodoDescription(e.target.value)} value={todoDescription} /> <Button variant="contained" color="primary" fullWidth onClick={() => { dispatch(addTodo(todoDescription)); setTodoDescription(""); }} > Add Item </Button> <List> {todoList.map((todo) => ( <ListItem key={todo.id}> <ListItemText style={{ textDecoration: todo.completed ? "line-through" : "none", }} > {todo.description} </ListItemText> <ListItemSecondaryAction> <IconButton onClick={() => { dispatch(removeTodo(todo.id)); }} > <DeleteIcon /> </IconButton> <Checkbox edge="end" value={todo.completed} onChange={() => { dispatch( setTodoStatus({ completed: !todo.completed, id: todo.id }) ); }} /> </ListItemSecondaryAction> </ListItem> ))} </List> </Container> );
} export default App;

Чтобы избежать путаницы, я не буду углубляться в детали Material-UI (для тех, кто не использовал его раньше, вам не нужно слишком подробно разбираться с тем, что делает каждый компонент). В приведенном выше коде важно то, как мы отправляем действия. Вы заметите зависимость названия действий от того, как мы назвали редукторы (addTodo, removeTodo, setTodoStatus).

Давайте сначала поговорим о хуке useDispatch и о том, как он подключается к различным элементам, которые мы создали в нашем файле todoSlice.ts.

В строке 46 мы отправляем действие addTodo. Нам нужно передать ему описание, и из-за того, как мы настроили его в срезе, ID и состояние Completed будут сгенерированы автоматически.

В строке 65 мы отправляем действие removeTodo. В этом сценарии нам нужно передать ему только идентификатор элемента задачи. Внутри преобразователя для удаления задачи у нас есть код, который будет соединять элемент задачи с элементом в массиве основе индекса.

В строке 75 мы отправляем действие setTodoStatus . В этом сценарии мы должны передать объект, который был определён в преобразователях в файле todoSlice.ts . Объект содержит как идентификатор, так и статус завершения, который мы хотим установить для элемента задачи. Нам нужны эти два элемента, потому что мы должны иметь возможность найти элемент списка дел в массиве, а также установить статус завершения на основе его значения.

Это объясняет, как мы используем диспетчеризацию, но теперь нам нужно понять, как хук useSelector работает в строке 25. Это важно, потому что мы используем этот хук для создания нашего списка.

В строке 25 вы увидите, что мы передаем стрелочную функцию в качестве параметра хуку useSelector. По сути, эта стрелочная функция описывает, как будут выглядеть данные, которые мы возвращаем. В этом случае мы хотим вернуть все состояние, поэтому я просто возвращаю состояние с другой стороны функции стрелки. Еще одна важная вещь для TypeScript — это стрелочная функция; параметр состояния имеет тип RootState, который мы определили ранее. По сути, это динамически генерируемый тип, полностью основанный на элементах, которые мы передаем в хранилище. Подводя итог, наша переменная todoList будет иметь тип Todo[] который мы можем сопоставить, чтобы сгенерировать список. Вы видите код для этого в строке 53.

И это почти все. У нас есть полностью функционирующее приложение с адаптивным состоянием с использованием Redux. Ниже приведён код на Github, если вы хотите клонировать мой репозиторий и использовать его для сравнения.

Заключение

Redux — это интересная и популярная библиотека, которую используют многие разработчики React. Даже если вы не планируете использовать его в своих проектах, может быть полезно знать и понимать его. По моему мнению, Redux Toolkit — это будущее многих приложений Redux. Он значительно сокращает количество шаблонов и в настоящее время является рекомендуемым подходом Redux. Надеюсь, вы получили полезную информацию из этого руководства о том, насколько удобным может быть Redux Toolkit.

Автор: Brian Francis

Источник: javascript.plainenglish.io

Редакция: Команда webformyself.

Читайте нас в Telegram, VK, Яндекс.Дзен