React + TypeScript: от основ до Jira-подобного проекта с Drag & Drop

Курс поможет перейти от базовых знаний веб-разработки к уверенной работе с React и TypeScript. Итогом станет портфолио-проект в стиле Jira (доска задач с drag & drop), который можно показать на собеседованиях на позицию junior.

1. Подготовка окружения и основы TypeScript для фронтенда

Подготовка окружения и основы TypeScript для фронтенда

Зачем мы начинаем с окружения и TypeScript

Ты хочешь сделать Jira-подобный проект с Drag & Drop и показывать его на собеседованиях. В таком проекте будет много сущностей (доски, колонки, задачи, пользователи, статусы), много взаимодействий (перетаскивание, фильтры, формы), и много мест, где легко ошибиться.

TypeScript помогает ловить ошибки до запуска приложения, а правильное окружение (Node.js, менеджер пакетов, IDE, линтеры) делает разработку быстрой и предсказуемой.

В этой статье мы:

  • поставим инструменты для React + TypeScript разработки
  • создадим проект на Vite
  • разберём базовые типы TypeScript, которые сразу пригодятся во фронтенде
  • заложим основу качества кода: форматирование, линтинг, структура
  • !Схема как инструменты (Vite, TypeScript, ESLint, Prettier) участвуют в разработке и запуске приложения

    Что установить

    Node.js

    Node.js нужен, чтобы ставить зависимости и запускать сборку/сервер разработки.

  • Ставь LTS-версию (Long-Term Support) — это стабильная ветка, на которую ориентируется большинство библиотек.
  • Проверка после установки:
  • Сайт:

  • Node.js
  • Менеджер пакетов: npm или pnpm

    Менеджер пакетов устанавливает зависимости (React, TypeScript, Vite и т.д.).

  • npm идёт вместе с Node.js и подходит для старта.
  • pnpm часто быстрее и экономит место на диске.
  • Если хочешь pnpm:

    Сайт:

  • pnpm
  • Git

    Git нужен для истории изменений и будущего портфолио на GitHub.

    Проверка:

    Сайт:

  • Git
  • VS Code

    Рекомендуемая среда разработки для React.

    Сайт:

  • Visual Studio Code
  • Рекомендуемые расширения:

  • ESLint (покажет проблемы в коде)
  • Prettier (форматирование)
  • TypeScript and JavaScript Language Features (обычно встроено)
  • Создаём React + TypeScript проект на Vite

    Почему Vite:

  • быстрый запуск dev-сервера
  • современная сборка
  • отличный шаблон для React + TypeScript
  • Создание проекта (выбери один вариант: npm или pnpm).

    Вариант с npm

    Вариант с pnpm

    После запуска открой адрес из консоли (обычно http://localhost:5173).

    Ссылки:

  • Vite
  • React
  • Что внутри проекта (минимальная ориентация)

    После создания проекта ты увидишь типичную структуру.

  • src/ — исходники приложения
  • src/main.tsx — точка входа (подключение React-приложения)
  • src/App.tsx — корневой компонент
  • tsconfig.json — настройки TypeScript
  • package.json — зависимости и команды
  • Команды в package.json называются scripts. Самые важные:

  • dev — запуск сервера разработки
  • build — сборка в продакшен
  • preview — локальный просмотр прод-сборки
  • База TypeScript для фронтенда

    TypeScript — это JavaScript плюс типы. Браузер TypeScript напрямую не запускает, поэтому код транспилируется в JavaScript (упрощённо: TypeScript-компилятор убирает типы и иногда преобразует синтаксис).

    Сайт:

  • TypeScript
  • Примитивные типы

    Самое частое:

  • string, number, boolean
  • null, undefined
  • Пример:

    Важно: в реальных проектах тип часто выводится автоматически, и явная аннотация не нужна.

    Массивы и объекты

    Массив строк:

    Объект с явно описанной формой:

    type и interface: что выбрать

    Оба способа описывают форму объекта.

  • interface удобен для публичных контрактов и расширения
  • type удобен для объединений (union), пересечений (intersection) и литеральных типов
  • На старте можно придерживаться простого правила:

  • для сущностей домена (Task, Column, Board) используй type
  • для пропсов компонента можно использовать и type, и interface — важно единообразие
  • Пример с interface:

    Литеральные типы и объединения (union)

    Во фронтенде часто есть поля с ограниченным набором значений: статус, приоритет, роль.

    Плюс такого подхода: ты не сможешь случайно присвоить status: "finished".

    Опциональные поля

    Иногда поле может быть, а может отсутствовать (например, описание задачи).

    description?: string означает: либо string, либо поля нет.

    null и undefined в UI

    Во фронтенде часто встречается ситуация: данные ещё не пришли.

    Когда используешь User | null, TypeScript заставляет обработать случай null.

    Функции: типы параметров и результата

    Если функция возвращает значение:

    Дженерики (generics) на понятном примере

    Дженерики полезны, когда функция работает с разными типами, но ты хочешь сохранить типобезопасность.

    Пример: взять первый элемент массива.

    Сужение типов (type narrowing)

    Если переменная имеет тип-объединение, нужно сузить тип проверкой.

    Этот паттерн ты будешь постоянно использовать для запросов к API.

    TypeScript в React: пропсы и события

    Типизация пропсов

    Ключевая идея: UI-компонент объявляет контракт, и TypeScript не даст тебе использовать компонент неправильно.

    Типизация обработчиков событий (коротко)

    Часто TypeScript сам выводит тип события, но иногда полезно знать явный тип.

    Базовая настройка качества кода

    В пет-проекте для собеседований важно показать инженерный подход: единый стиль, предсказуемые проверки.

    ESLint и Prettier

    В шаблоне Vite для React TS линтер часто уже есть. Проверить можно так:

    Если линта нет, его обычно добавляют отдельно, но в рамках этой статьи достаточно:

  • запускать lint перед коммитами
  • настроить автоформатирование через Prettier в VS Code
  • Официальные сайты:

  • ESLint
  • Prettier
  • EditorConfig

    EditorConfig помогает одинаково форматировать базовые вещи (отступы, переводы строк) во всех редакторах.

    Сайт:

  • EditorConfig
  • Git: минимальный рабочий процесс

    1) Инициализируй репозиторий:

    2) Сделай первый коммит:

    Дальше мы будем делать маленькие логичные коммиты под фичи (доска, колонки, карточки, DnD), чтобы история выглядела профессионально.

    Итоги и что дальше

    Теперь у тебя:

  • установлены базовые инструменты
  • создан React + TypeScript проект на Vite
  • есть понимание ключевых типов TypeScript, необходимых для UI и данных
  • В следующей статье мы начнём строить основу приложения: структуру src, базовые компоненты, и модель данных для нашей Jira-подобной доски (Board → Columns → Tasks), чтобы потом уверенно прийти к Drag & Drop и работе с состоянием.

    2. React основы: компоненты, JSX, state, props, хуки

    React основы: компоненты, JSX, state, props, хуки

    Как React вписывается в наш Jira-проект

    В прошлой статье ты настроил окружение и разобрал базу TypeScript. Теперь следующий шаг: понять, как React строит интерфейс из компонентов и как мы будем управлять состоянием доски.

    В Jira-подобном приложении почти всё естественно раскладывается на компоненты:

  • страница доски
  • список колонок
  • колонка
  • карточка задачи
  • формы создания и редактирования
  • модальные окна
  • Нам нужно научиться:

  • описывать UI через компоненты и JSX
  • передавать данные вниз через props
  • хранить изменяемые данные в state
  • реагировать на события пользователя
  • подключать хуки для эффекта, ссылок на DOM и оптимизации
  • Базовая документация React:

  • React документация
  • Компоненты

    Компонент в React чаще всего это функция, которая получает входные данные и возвращает разметку.

    Минимальный компонент:

    Ключевые идеи:

  • компонент должен быть предсказуемым
  • React вызывает компонент, получает результат и рисует его
  • когда данные меняются, React перерисовывает нужные части
  • Компонент как функция от данных

    Практическая модель мышления:

  • props это вход
  • JSX это выход
  • Если props те же, компонент должен вести себя одинаково. Позже это поможет писать понятный код и проще отлаживать интерфейс.

    JSX

    JSX это синтаксис, который позволяет писать разметку рядом с логикой. Внутри это превращается в вызовы React.

    Выражения внутри JSX

    В JSX можно вставлять выражения в фигурных скобках.

    Важно:

  • можно вставлять выражения, но не инструкции
  • условный рендер обычно делается через тернарный оператор или логическое И
  • Пример условия:

    Атрибуты в JSX

    Несколько частых отличий от HTML:

  • className вместо class
  • htmlFor вместо for
  • style принимает объект
  • Списки и ключи

    Когда рисуешь массив элементов, у каждого должен быть key.

    Почему это важно:

  • ключ помогает React правильно сопоставлять элементы при обновлениях
  • нельзя использовать индекс массива как key, если список может меняться местами
  • Props

    Props это данные, которые компонент получает от родителя.

    Свойства props:

  • передаются сверху вниз
  • считаются неизменяемыми внутри компонента
  • позволяют переиспользовать компоненты
  • Типизация props в TypeScript

    Плюсы для собеседований:

  • компонент явно объявляет контракт
  • ошибки использования ловятся компилятором
  • Props children

    React позволяет вкладывать разметку внутрь компонента. Это приходит как children.

    State

    State это изменяемые данные, которые принадлежат компоненту. Когда state меняется, React перерисовывает компонент.

    Хук useState

    Что важно понять:

  • setCount запускает обновление и перерисовку
  • обновления могут быть сгруппированы, поэтому полезна функциональная форма
  • Функциональная форма обновления:

    Она особенно важна, когда новое значение зависит от предыдущего.

    Иммутабельность в state

    В React состояние обычно обновляют создавая новые объекты и массивы, а не изменяя старые.

    Пример обновления массива задач:

    Почему так:

  • React проще сравнивать ссылки, чтобы понять, что изменилось
  • мутации могут приводить к трудноуловимым багам
  • События и управляемые формы

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

    Здесь:

  • значение инпута хранится в state
  • onChange обновляет state
  • UI всегда отражает состояние
  • Хуки

    Хуки это функции React, которые подключают состояние и жизненный цикл к функциональным компонентам.

    Документация:

  • React hooks
  • Правила хуков

    Основные правила:

  • хуки вызываются только на верхнем уровне компонента
  • хуки нельзя вызывать внутри условий, циклов и вложенных функций
  • хуки вызываются только в React-компонентах и кастомных хуках
  • Причина простая: React привязывает состояние к порядку вызовов хуков.

    useEffect

    useEffect выполняет побочные эффекты: запросы, подписки, работа с localStorage, синхронизация с внешним миром.

    Пример: загрузить данные при открытии страницы.

    Как читать второй аргумент useEffect:

  • [] означает запустить эффект один раз после первого рендера
  • [x, y] означает запускать эффект, когда меняются x или y
  • если аргумент не передан, эффект будет после каждого рендера
  • Функция возврата из эффекта это очистка, она вызывается при размонтировании компонента или перед следующим запуском эффекта.

    useRef

    useRef хранит значение, которое не должно вызывать перерисовку при изменении. Также часто используется для доступа к DOM-элементу.

    Для нашего проекта это пригодится, например, чтобы фокусировать поле ввода при открытии модалки редактирования задачи.

    useMemo и useCallback

    Эти хуки нужны для оптимизации, но использовать их стоит осмысленно.

  • useMemo запоминает результат вычисления
  • useCallback запоминает ссылку на функцию
  • Пример с вычислением:

    Пример со стабильной функцией, которую передаёшь вниз:

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

    Кастомные хуки

    Кастомный хук это функция, которая:

  • начинается с use
  • внутри использует другие хуки
  • инкапсулирует повторяемую логику
  • Пример: хранение состояния в localStorage.

    Позже мы применим это, чтобы сохранять доску между перезагрузками, даже без бекенда.

    Как собрать это в основу Jira-доски

    Нам важно заранее спроектировать поток данных.

    !Дерево компонентов и направление потока данных

    Обычно данные живут там, где ими удобнее управлять:

  • state доски находится в BoardPage
  • Column и TaskCard получают данные через props
  • действия пользователя поднимаются наверх колбэками
  • Пример минимальных типов для старта (мы будем расширять их дальше):

    И пример управления на верхнем уровне:

    Это ещё не Drag & Drop, но здесь уже заложена правильная архитектура:

  • единый источник правды для данных доски
  • чистые презентационные части через props
  • изменения делаются через функции, которые обновляют state иммутабельно
  • Итоги

    Теперь у тебя есть базовые кирпичики React, которые понадобятся для Jira-проекта:

  • компоненты и JSX для построения UI
  • props для передачи данных и колбэков
  • state через useState для изменяемых данных
  • хуки useEffect, useRef, useMemo, useCallback для реальных задач приложения
  • понимание потока данных: props вниз, события вверх
  • Дальше мы начнём превращать эту основу в структуру проекта: выделим компоненты доски, оформим типы домена, добавим более удобное управление состоянием, а потом подойдём к Drag & Drop.

    3. React + TypeScript на практике: типизация компонентов и работа с API

    React + TypeScript на практике: типизация компонентов и работа с API

    Зачем это нужно именно для Jira-проекта

    В прошлых статьях ты настроил окружение, освоил базовый TypeScript и разобрал фундамент React: компоненты, props, state, хуки.

    Теперь мы делаем следующий шаг к Jira-подобному проекту:

  • научимся типизировать компоненты так, чтобы ошибки ловились на этапе разработки
  • разберём типизацию обработчиков событий и форм
  • построим минимальный слой работы с API на fetch с типами
  • научимся хранить состояния загрузка / ошибка / данные типобезопасно
  • подготовим структуру, которую потом удобно расширить под Drag & Drop и реальный бэкенд
  • Ключевая идея: в React ты постоянно связываешь данные и UI, а TypeScript помогает сделать эту связь надёжной.

    !Как данные и события проходят через слой API и компоненты

    Типизация компонентов: минимальный стандарт качества

    Типизация props

    Договоримся о простом правиле для проекта:

  • доменные сущности описываем через type
  • props компонентов тоже можно описывать через type (главное — единообразие)
  • Пример доменной модели (упрощённая):

    Компонент карточки:

    Почему так хорошо для собеседований:

  • видно контракт компонента
  • невозможно случайно забыть onRemove или передать не тот тип
  • удобно переиспользовать и рефакторить
  • Типизация children

    Общий UI-компонент-контейнер:

    ReactNode покрывает почти всё, что можно отрендерить.

    Типизация событий: формы без боли

    В Jira-проекте много форм: создание задачи, редактирование, поиск.

    onChange для input

    onSubmit для формы

    Важно: у submit нужно отменять дефолтное поведение браузера.

    Типобезопасные состояния загрузки: data / loading / error

    Когда ты работаешь с API, у экрана обычно есть 3 состояния:

  • данные загружаются
  • данные загружены
  • произошла ошибка
  • Чтобы не путаться в isLoading и error одновременно, удобно сделать дискриминирующее объединение.

    Теперь TypeScript заставляет тебя обработать все ветки.

    Работа с API: минимальный fetch-клиент с типами

    Для начала мы сделаем API-слой так, чтобы его можно было легко заменить:

  • сегодня это может быть мок или json-server
  • завтра — реальный бэкенд
  • Источник про fetch:

  • MDN: Fetch API
  • Базовая функция fetchJson

    fetch сам по себе не знает, что пришло с сервера. TypeScript тоже не знает. Поэтому нам нужно:

  • проверить response.ok
  • распарсить JSON
  • аккуратно привести тип
  • Подключаем API в React: загрузка доски на странице

    Соберём всё в компоненте BoardPage.

    Что здесь важно:

  • AsyncState<Board> заставляет компонент быть честным: нельзя отрисовать доску, пока её нет
  • cancelled защищает от ситуации, когда компонент размонтировали, а промис завершился позже
  • Практика: типизация колбэков для будущего Drag & Drop

    Даже до Drag & Drop полезно типизировать действия доски.

    Например, перемещение задачи между колонками:

    И контракт для компонента колонки:

    Почему это важно заранее:

  • когда ты подключишь Drag & Drop библиотеку, она будет отдавать события, которые нужно перевести в понятное приложению действие
  • типизированный payload делает это действие единым и проверяемым
  • Переменные окружения для API в Vite

    Чтобы не хардкодить базовый URL, в Vite принято использовать import.meta.env.

    Документация:

  • Vite: Env Variables and Modes
  • Пример:

    1) Создай файл .env:

    2) Используй в коде:

    Важно: переменные, доступные в браузере, в Vite должны начинаться с VITE_.

    Итоги

    Теперь у тебя есть практический фундамент, без которого Jira-проект быстро превратится в хаос:

  • типизация props, children, событий и колбэков
  • понятная модель AsyncState<T> для загрузки данных
  • минимальный API-слой на fetch с обработкой ошибок
  • разделение DTO и доменной модели через маппинг
  • подготовка контрактов действий (например, для будущего Drag & Drop)
  • Дальше логичный шаг курса: начать собирать реальные экранные компоненты доски и продумать управление состоянием так, чтобы Drag & Drop можно было добавить без переписывания половины приложения.

    4. Архитектура и состояние приложения: роутинг, формы, управление данными

    Архитектура и состояние приложения: роутинг, формы, управление данными

    Как эта статья приближает тебя к Jira-проекту

    В предыдущих статьях ты:

  • настроил окружение и базу TypeScript
  • разобрал React-компоненты, props, state, хуки
  • сделал типобезопасный API-слой и состояния loading/error/success
  • Теперь нужно собрать это в архитектуру, которая выдержит рост проекта. Jira-подобное приложение быстро усложняется:

  • появляются страницы и маршруты
  • добавляются формы создания и редактирования
  • растёт объём данных (доски, колонки, задачи)
  • появляются сложные операции (перемещение, сортировка, Drag & Drop)
  • Цель статьи: выстроить структуру и подход к состоянию так, чтобы Drag & Drop добавился как ещё один способ вызвать действие, а не как причина переписать весь проект.

    !Общая схема слоёв и направления потока данных

    Принципы, на которых будем строить проект

    Чтобы проект выглядел уверенно на собеседованиях, держимся трёх принципов.

  • Разделяй ответственность: UI-компоненты отдельно, бизнес-логика отдельно, API отдельно.
  • Единый источник правды для данных доски: состояние доски живёт в одном месте и изменяется только через действия.
  • Чистые функции для сложных операций: перемещение задач, перестановка, изменение порядка должны быть тестируемыми и не привязанными к DOM.
  • Роутинг: страницы, вложенные маршруты и параметры

    Зачем роутинг Jira-клону

    Даже если приложение сначала маленькое, правильный роутинг сразу даёт тебе:

  • отдельные страницы для списка досок и доски
  • возможность делиться ссылкой на конкретную доску
  • возможность открывать задачу по URL (удобно для портфолио)
  • Для React чаще всего используют react-router-dom.

    Документация:

  • React Router
  • Установка

    Базовая структура страниц

    Сделаем минимальный набор страниц:

  • BoardListPage — список досок (пока можно мок)
  • BoardPage — конкретная доска
  • NotFoundPage — если маршрут не найден
  • И общий layout:

  • AppLayout — шапка, контейнер, место для рендера страниц
  • Рекомендуемая структура папок

    Один из понятных вариантов (не единственный, но практичный):

  • src/app — вход в приложение, провайдеры, роутинг
  • src/pages — страничные компоненты
  • src/features — функциональные модули (например, board)
  • src/shared — переиспользуемые UI-элементы, утилиты
  • src/api — API-клиент, DTO, мапперы
  • src/model — доменные типы и общие модели (AsyncState<T>, и т.д.)
  • Пример дерева:

    Создаём router

    src/app/router.tsx:

    src/app/App.tsx:

    src/app/AppLayout.tsx:

    Параметры маршрута в TypeScript

    В BoardPage нам нужен boardId.

    src/pages/BoardPage.tsx:

    Важно понимать: useParams() возвращает значения как string | undefined, потому что параметр может отсутствовать при некорректном URL.

    Query params для фильтров

    Фильтры (например, поиск по задачам) удобно хранить в query string.

    Документация:

  • React Router useSearchParams
  • Пример (поиск по задачам):

    Такой подход выглядит профессионально:

  • фильтр сохраняется при перезагрузке
  • ссылкой можно поделиться
  • Управление данными: где хранить состояние и как его менять

    Что такое состояние в Jira-проекте

    Тебе нужно различать несколько видов состояния:

  • UI state: открыта ли модалка, значение инпута, выбранный фильтр
  • Server state: доска, колонки, задачи (то, что пришло от API)
  • Derived state: вычисления на основе данных (например, список задач колонки)
  • Сильный паттерн для доски: хранить данные нормализованно.

    Нормализованная модель доски

    Плохой вариант для роста: хранить задачи прямо вложенными в колонки. Хороший вариант: задачи в Record, а колонки хранят taskIds.

    src/features/board/model/types.ts:

    Плюсы:

  • задача существует в одном месте
  • перемещение — это изменение массивов taskIds, а не копирование задач
  • удобно делать быстрый поиск tasks[id]
  • Почему useReducer лучше, чем много useState

    Для доски у тебя быстро появятся действия:

  • создать задачу
  • удалить задачу
  • переименовать задачу
  • переместить задачу между колонками
  • переставить задачу внутри колонки
  • Если это держать на куче useState, логика размажется по компонентам.

    useReducer помогает:

  • собрать изменения состояния в одном месте
  • сделать действия явными
  • подготовить почву под Drag & Drop (DnD событие будет просто вызывать dispatch({ type: "task/move", ... }))
  • Документация:

  • React useReducer
  • Действия и reducer

    src/features/board/model/reducer.ts:

    Ключевой момент для будущего Drag & Drop: task/move — чистое, типизированное действие. DnD-библиотека будет только поставщиком координат и индексов.

    Селекторы: не смешивай вычисления с UI

    Если компоненты постоянно делают map/filter прямо внутри JSX, код быстро становится грязным.

    Сделай маленькие функции-селекторы.

    src/features/board/model/selectors.ts:

    Где держать состояние: локально, в контексте или на странице

    Рекомендуемая схема для старта

  • состояние доски держим на уровне BoardPage
  • UI-компоненты получают данные через props
  • действия передаются колбэками
  • Это уже достаточно для сильного пет-проекта.

    Когда проект вырастет, можно аккуратно добавить Context, чтобы не прокидывать dispatch через много уровней.

    Документация:

  • React Context
  • Минимальный BoardProvider (опционально)

    src/features/board/model/BoardContext.tsx:

    И использование на странице:

    Формы: создание и редактирование задач без хаоса

    Контролируемые поля и типизация

    Для старта контролируемые формы — лучший выбор: минимум магии, максимум понимания.

    Пример формы создания задачи.

    src/features/board/ui/CreateTaskForm.tsx:

    Здесь важно:

  • onSubmit типизирован как React.FormEvent<HTMLFormElement>
  • валидация простая, но явная
  • родитель решает, что значит создать задачу (через onCreate)
  • Как форма связывается с reducer

    На уровне колонки:

    А на уровне страницы:

    Это и есть правильная архитектура: форма не знает про reducer, она знает про событие.

    Когда стоит перейти на библиотеку форм

    Если в проекте появятся большие формы (например, редактирование задачи с несколькими полями, валидацией, состояниями dirty/touched), можно рассмотреть библиотеку.

    Популярный вариант:

  • React Hook Form
  • Для джун-проекта достаточно контролируемых форм, но упомянуть библиотеку в README проекта будет плюсом.

    Управление загрузкой данных: где место API и как не сломать UI

    У тебя уже есть AsyncState<T> и fetchJson<T>. Архитектурно важно:

  • не вызывать fetch глубоко внутри маленьких компонентов
  • загружать данные на уровне страницы или feature-слоя
  • хранить результат в состоянии страницы
  • Типовой поток:

  • BoardPage читает boardId из URL
  • вызывает getBoard(boardId)
  • кладёт результат в AsyncState<BoardState>
  • рендерит loading/error/success
  • Это помогает:

  • избежать гонок и “половинчатого UI”
  • централизовать обработку ошибок
  • Если позже захочешь прокачать проект, можно внедрить библиотеку для server state.

  • TanStack Query
  • Но на текущем этапе курса полезнее сначала научиться делать это руками, чтобы понимать механику.

    Подготовка к модалке задачи через роутинг

    В Jira задача часто открывается в модальном окне, но URL при этом меняется. Это даёт:

  • возможность обновить страницу и остаться на задаче
  • возможность шарить ссылку на задачу
  • Для этого обычно делают маршрут вида:

  • /boards/:boardId/tasks/:taskId
  • И рендерят TaskModal.

    Схема маршрутов:

    !Визуализация идеи модалки через маршрут

    Реализация такого паттерна может быть разной. Самый простой старт:

  • сделать отдельную страницу TaskPage или TaskModalRoute
  • позже улучшить до модалки поверх доски
  • Главное сейчас: вынести открытие задачи в URL.

    Итоги

    После этой статьи у тебя должна сложиться рабочая картина, как строить Jira-подобный фронтенд:

  • роутинг через react-router-dom: страницы, параметры, query string
  • структура проекта по слоям: pages, features, api, model, shared
  • состояние доски через useReducer и типизированные действия
  • селекторы для вычислений вместо логики в JSX
  • формы как отдельные компоненты, которые вызывают события, а не меняют состояние напрямую
  • Дальше, когда мы добавим Drag & Drop, у тебя уже будет готовое место, куда “приземлять” результат перетаскивания: dispatch({ type: "task/move", payload: ... }).

    5. Jira-подобный проект: канбан, drag & drop, тесты и деплой

    Jira-подобный проект: канбан, drag & drop, тесты и деплой

    Как эта статья собирает курс в законченный проект

    В прошлых статьях ты заложил фундамент:

  • TypeScript для типобезопасных моделей и контрактов
  • React-основы (компоненты, props, state, хуки)
  • типизированный API-слой и состояния loading/error/success
  • архитектуру: pages/features/shared, роутинг, нормализованное состояние доски и useReducer
  • Теперь мы доводим Jira-подобный проект до уровня, который не стыдно показывать на собеседовании:

  • делаем канбан-доску с перетаскиванием задач (между колонками и внутри колонки)
  • добавляем тесты (логика + UI) и минимальный CI
  • деплоим проект и оформляем его как портфолио
  • !Как событие Drag & Drop превращается в типизированное действие и обновляет состояние

    Канбан: требования к функциональности

    Минимальный набор, который выглядит как настоящий продукт:

  • колонки (например, Todo, In progress, Done)
  • задачи (карточки)
  • создание задачи в колонку
  • редактирование заголовка задачи (хотя бы в модалке или на отдельном маршруте)
  • перемещение задачи:
  • - внутри колонки (изменение порядка) - между колонками (смена статуса)

    Ключевая мысль: Drag & Drop не должен менять архитектуру. Он должен только генерировать событие, которое преобразуется в твой доменный экшен task/move.

    Выбор Drag & Drop библиотеки

    Для React + TypeScript в 2026 году хороший практичный вариант для пет-проекта — dnd-kit:

  • современная архитектура и активная экосистема
  • удобная типизация
  • нормальная кастомизация
  • Официальный сайт:

  • dnd-kit
  • Установка:

    Подготовка идентификаторов и моделей для DnD

    У тебя уже есть нормализованная модель:

    Для DnD важно, чтобы каждая перетаскиваемая сущность имела стабильный id. Мы будем перетаскивать задачи, поэтому используем taskId как UniqueIdentifier.

    Практический совет: держи DnD id как строку TaskId. Не усложняй префиксами, пока не перетаскиваешь колонки.

    UI-слой: делаем задачи сортируемыми

    Общее устройство dnd-kit (очень коротко)

  • DndContext — оборачивает область, где работает DnD
  • useSortable — делает конкретный элемент сортируемым
  • SortableContext — сообщает, какие элементы в контейнере и в каком порядке
  • Ссылки:

  • dnd-kit sortable
  • Компонент карточки как sortable

    src/features/board/ui/TaskCardSortable.tsx:

    Здесь важны две вещи:

  • id: task.id — ключ, по которому dnd-kit понимает, что именно мы таскаем
  • attributes и listeners — то, что делает элемент реально draggable
  • Колонка как контейнер сортировки

    src/features/board/ui/ColumnView.tsx:

    Критично:

  • items={column.taskIds} должен совпадать с тем порядком, который хранится в состоянии
  • Превращаем DragEnd в dispatch({ type: "task/move" })

    Проблема, которую нужно решить

    DnD библиотека отдаёт тебе:

  • active.id — что тащим
  • over?.id — над чем отпустили
  • Но reducer ожидает доменное действие:

    Значит нам нужен адаптер: функция, которая по BoardState и DragEndEvent строит MoveTaskPayload.

    Утилиты поиска

    src/features/board/model/dnd.ts:

    Обработчик onDragEnd

    src/features/board/ui/BoardDndRoot.tsx:

    Что здесь происходит:

  • active.id считаем taskId.
  • Пытаемся определить fromColumnId через поиск по state.columns.
  • over.id — это либо другая задача, либо та же. Мы делаем упрощённый вариант: считаем, что over.id тоже taskId.
  • toIndex — индекс overId в целевой колонке.
  • Вызываем reducer-экшен task/move.
  • > В реальной Jira ты можешь дропать в пустую колонку. Для этого добавляют отдельную droppable-зону колонки (не карточки). Это следующий шаг улучшения, когда база уже работает.

    Собираем страницу доски

    На уровне BoardPage у тебя уже есть useReducer(boardReducer, initial).

    Теперь ты оборачиваешь UI в BoardDndRoot:

    Если reducer из прошлой статьи реализован корректно, перетаскивание начнёт менять порядок задач и перемещать их между колонками.

    Качество проекта: тесты

    Тесты в Jira-проекте важны не потому, что без них нельзя. Они важны, потому что:

  • у тебя появляется чистая бизнес-логика (reducer), которую легко тестировать
  • на собеседовании тесты сразу показывают инженерную культуру
  • Инструменты

    Рекомендуемый стек под Vite:

  • Vitest
  • React Testing Library
  • Установка:

    Добавь в vite.config.ts (минимально):

    src/test/setup.ts:

    Самые выгодные тесты: reducer

    Потому что reducer:

  • чистая функция
  • без DOM
  • без асинхронщины
  • src/features/board/model/reducer.test.ts:

    UI-тесты: проверяй не Drag & Drop, а важные сценарии

    Техническая реальность: тестировать DnD через unit/integration тесты сложно и дорого.

    Хорошая стратегия для пет-проекта:

  • reducer тестируем unit-тестами
  • UI тестируем на:
  • - рендер колонок и задач - создание задачи - открытие модалки/переход по роуту
  • DnD покрываем либо reducer-тестами, либо E2E
  • Если хочешь E2E, бери Playwright:

  • Playwright
  • Минимальный CI (чтобы на GitHub выглядело серьёзно)

    Даже один workflow, который гоняет lint и test, сильно улучшает впечатление.

    Официальная документация:

  • GitHub Actions
  • Пример .github/workflows/ci.yml:

    Деплой: как опубликовать проект

    Цель деплоя для собеседований:

  • дать ссылку на работающий проект
  • показать, что ты умеешь доводить задачу до результата
  • Вариант: Vercel

    Документация:

  • Vercel: Deploying Vite
  • Что обычно нужно:

  • импортировать GitHub репозиторий
  • build command: npm run build
  • output directory: dist
  • Вариант: Netlify

    Документация:

  • Netlify: Vite
  • Если есть API

    Если твой фронт ходит в API, заранее продумай:

  • VITE_API_BASE_URL в .env и настройках хостинга
  • CORS на бэкенде
  • Про переменные окружения в Vite:

  • Vite: Env Variables and Modes
  • Финальный штрих для портфолио

    Чтобы проект реально работал на тебя на собеседованиях, добавь в репозиторий:

  • README.md:
  • - краткое описание - список фич - ссылка на демо - стек - как запустить локально
  • несколько скриншотов
  • понятную историю коммитов (не один коммит на весь проект)
  • !Что должно быть у пет-проекта, чтобы он выглядел как продукт

    Итоги

    Теперь у тебя есть полная цепочка от архитектуры до релиза:

  • канбан с нормализованными данными
  • Drag & Drop как адаптер событий в dispatch({ type: "task/move" })
  • тесты, которые выгоднее всего писать: reducer и ключевые UI-сценарии
  • CI, который проверяет качество
  • деплой, чтобы давать ссылку на демо
  • С этого момента проект можно итеративно улучшать: пустые колонки как droppable, перетаскивание колонок, модалка задачи через роутинг, фильтры, поиск, сохранение на сервер, права доступа. Но база уже соответствует уровню сильного джуна: архитектура, типы, тестируемая логика, деплой.