Шаг 1. Установка React-проекта
npm create vite@latest todo-app -- --template react-ts
cd todo-app
npm install
Шаг 2. Структура проекта
├── eslint.config.js
├── index.html
├── package.json
├── public
│ └── vite.svg
├── README.md
├── src
│ ├── App.css
│ ├── App.jsx
│ ├── assets
│ ├── index.css
│ └── main.jsx
└── vite.config.js
- App.jsx — главный компонент.
- main.jsx — точка входа (React подключается к HTML).
Шаг 3. Пишем простую версию ToDo
В App.jsx
начнём с базового:
import { useState } from "react";
function App() {
const [tasks, setTasks] = useState([]); // массив задач
const [newTask, setNewTask] = useState(""); // текст новой задачи
const addTask = () => {
if (newTask.trim() === "") return;
setTasks([...tasks, newTask]);
setNewTask("");
};
return (
<div style="{{" padding:="" "20px",="" fontfamily:="" "sans-serif"="" }}="">
<h1>📋 Мой список задач</h1>
<input value="{newTask}" onchange="{(e)" ==""> setNewTask(e.target.value)}
placeholder="Введите задачу..."
/>
<button onclick="{addTask}">Добавить</button>
<ul>
{tasks.map((task, index) => (
<li key="{index}">{task}</li>
))}
</ul>
</div>
);
}
export default App;
В src/App.css
:
.app-container {
max-width: 500px;
margin: 40px auto;
padding: 20px;
font-family: sans-serif;
background: #f5f5f5;
border-radius: 8px;
}
.input-container {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
input {
flex: 1;
padding: 8px;
font-size: 16px;
}
button {
padding: 8px 16px;
font-size: 16px;
cursor: pointer;
}
.task-list {
list-style: none;
padding: 0;
}
.task-list li {
padding: 6px 0;
border-bottom: 1px solid #ddd;
}
Шаг 4. Запускаем проект
npm run dev
Откроется страница с полем ввода, кнопкой и списком задач.
Шаг 5. Добавляем удаление задачи
В App.jsx
меняем логику так:
import { useState } from "react";
import "./App.css";
function App() {
const [tasks, setTasks] = useState([]);
const [newTask, setNewTask] = useState("");
const addTask = () => {
if (newTask.trim() === "") return;
setTasks([...tasks, { text: newTask, completed: false }]);
setNewTask("");
};
const deleteTask = (index) => {
setTasks(tasks.filter((_, i) => i !== index));
};
const toggleTask = (index) => {
setTasks(
tasks.map((task, i) =>
i === index ? { ...task, completed: !task.completed } : task
)
);
};
return (
📋 Мой список задач
setNewTask(e.target.value)}
/>
{tasks.map((task, index) => (
-
toggleTask(index)}>{task.text}
))}
);
}
export default App;
Оператор … («spread» — распыление) берёт все элементы массива и вставляет их в новом месте.
const arr1 = [1, 2, 3];
const arr2 = […arr1, 4];
console.log(arr2); // [1, 2, 3, 4]
В App.css
добавим:
.task-list li {
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
}
.task-list li span {
flex: 1;
}
- Теперь задача хранится как объект
{ text, completed }
. - Клик по тексту задачи переключает её состояние (выполнена / не выполнена).
- Кнопка ❌ удаляет задачу.
- React при каждом изменении отрисовывает только изменившиеся элементы, а не всю страницу — это ключевая идея.
Шаг 6. Хранение списка задач в localStorage
План действий
- При загрузке страницы — читать задачи из
localStorage
и класть их вtasks
. - При каждом изменении задач — сохранять их в
localStorage
.
Для этого используем хук useEffect
, который в React позволяет выполнять побочные действия при рендере.
Код с localStorage
Открой App.jsx
и обнови его так:
import { useState, useEffect } from "react";
import "./App.css";
function App() {
const [tasks, setTasks] = useState(() => {
const saved = localStorage.getItem("tasks");
return saved ? JSON.parse(saved) : [];
});
const [newTask, setNewTask] = useState("");
// Сохраняем задачи при каждом изменении
useEffect(() => {
localStorage.setItem("tasks", JSON.stringify(tasks));
}, [tasks]);
const addTask = () => {
if (newTask.trim() === "") return;
setTasks([...tasks, { text: newTask, completed: false }]);
setNewTask("");
};
const deleteTask = (index) => {
setTasks(tasks.filter((_, i) => i !== index));
};
const toggleTask = (index) => {
setTasks(
tasks.map((task, i) =>
i === index ? { ...task, completed: !task.completed } : task
)
);
};
return (
📋 Мой список задач
setNewTask(e.target.value)}
/>
{tasks.map((task, index) => (
-
toggleTask(index)}>{task.text}
))}
);
}
export default App;