8 - Render алгоритм на основе структур данных

Оценить качество материала и подачу материала автором видео:

Front-end

Трудоустройтесь middle front-end разработчиком на React JS (TypeScript) за 12-16 месяцев обучения с ежедневной менторской поддержкой в формате видео 1 на 1 и коммерческими проектами в портфолио

Перейти на курс
Front-end

Back-end

Трудоустройтесь middle back-end разработчиком за 12-16 месяцев обучения с ежедневной менторской поддержкой в формате видео 1 на 1 и коммерческими проектами в портфолио

Перейти на курс
Back-end

Карьерный бустер

Получите коммерческий опыт на реальных стартапах, прокачайте tech & soft навыки, научитесь работать в команде, проходить собеседования и получите первую работу в IT!

Перейти на курс
Карьерный бустер

Основы Front-end

Сделайте первый шаг в IT, освоив базовые знания разработки и научившись создавать небольшие проекты на JavaScript

Перейти на курс
Основы Front-end

Основы Back-end

Сделайте первый шаг в IT, освоив базовые знания разработки. Без опыта. Без математики. Только практика: JavaScript, SQL, Node JS, база данных

Перейти на курс
Основы Back-end

Данные и алгоритм рендера

Автор конспекта: Арина Василевская

Существует два мира: данные и алгоритм

  • Мир данных (state) — хранение информации, с которой работает приложение. Данные являются первичными. Данные, лежащие в основе state, переменчивы, они меняются как живой организм. State позволяет реализации быть интерактивной. React реагирует на изменения нашего state, и приложение перерисовывается.

  • Мир алгоритмов (render algoritm) — преобразование данных в интерфейс (JSX/React-элементы). Алгоритм зависит от данных, на их основании работает, и с ними взаимодействует.

Наша задача, как разработчиков быть архитектором данных и алгоритмов. Разработчик не может думать только о данных, он также должен учитывать, как эти данные будут использоваться в алгоритме, стремясь к удобству, понятности и эффективности с обеих сторон – как во frontend, так и в backend:

  • Адаптация алгоритма под данные: Если формат данных задан внешним источником (например, backend), и его нельзя изменить, алгоритм должен быть подстроен под существующую структуру данных.
  • Адаптация данных под алгоритм: Если становится понятно, что алгоритм будет очень неудобный при работе с входящими данными, разработчик может адаптировать структуру данных (например, полученную с backend) под ту, с которой удобно работать frontend, выполняя преобразования.

Структура данных и алгоритм отрисовки

Для задачи отрисовки списка треков идеально подходящей структурой данных является массив объектов, где каждый объект описывает наш трек. Этот массив выделяется в переменную и называется состоянием (state), от которого зависит отрисовка.

objectsArray

App.tsx
// ...
const tracks = [
  //структура данных
  {
    id: 1,
    title: "Musicfun soundtrack",
    url: "https://musicfun.it-incubator.app/api/samurai-way-soundtrack.mp3",
  },
  {
    id: 2,
    title: "Musicfun soundtrack instrumental",
    url: " https://musicfun.it-incubator.app/api/samurai-way-soundtrack-instrumental.mp3",
  },
]
//...

Такая структура позволяет алгоритму итерироваться по каждому треку в массиве и на его основании формировать соответствущий JSX-элемент. На основе каждого объекта создается элемент списка (<li>), внутри которого есть заголовок(<div>) и трек(<audio>)

App.tsx
//...
<ul>
  {tracks.map(
    (
      track, // алгоритм
    ) => (
      <li key={track.id}>
        <div>{track.title}</div>
        <audio controls src={track.url}></audio>
      </li>
    ),
  )}
</ul>
//...

Обработка разных состояний данных

Выделим три состояния:

1. Нет данных

emptyData

При пустом массиве в state на странице должно отобразится сообщение: 'Нет треков'. Это реализуется с помощью раннего возврата(early return) из компонента:

App.tsx
//...
const tracks = []
function App() {
  if (tracks.length === 0) {
    return (
      <div>
        <h1>Musicfun player</h1>
        <span>No tracks</span>
      </div>
    )
  }
 
  return (
    <>
      <h1>Musicfun player</h1>
      <ul>
        {tracks.map((track) => (
          <li key={track.id}>
            <div>{tracks.title}</div>
            <audio controls src={tracks.url}></audio>
          </li>
        ))}
      </ul>
    </>
  )
}
 
export default App

2. Загрузка данных

Когда данные загружаются с сервера, важно различать два разных состояния:

  • Данных точно нет → получен пустой массив.
  • Данные ещё не получены → запрос в процессе, и результат неизвестен.

В ситуации ожидания неправильно показывать пользователю, что данных нет. Корректнее отобразить, что приложение загружает информацию — например, вывести Loader. Это даёт пользователю понять, что идёт процесс получения данных, а не ошибка или пустой результат.

loadingData

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

  • Если tracks === null, это значит, что данные ещё не инициализированы и приложение находится в процессе их получения. В таком случае рендер должен вывести Loading...

  • Такая логика реализуется также через early return: добавляется условие if (tracks === null), при котором возвращается Loading....

App.tsx
//...
const tracks = null
function App() {
  if (tracks === null) {
    return (
      <div>
        <h1>Musicfun player</h1>
        <span>Loading...</span>
      </div>
    )
  }
 
  return (
    <>
      <h1>Musicfun player</h1>
      <ul>
        {tracks.map(
          (
            track, //алгоритм
          ) => (
            <li key={track.id}>
              <div>{tracks.title}</div>
              <audio controls src={tracks.url}></audio>
            </li>
          ),
        )}
      </ul>
    </>
  )
}
//...

3. Интерактивность: выделение трека

Когда на экране что-то изменяется (элемент подсветился, исчез или появился), разработчику важно определить: какие данные должны меняться и как именно, чтобы алгоритм отрисовки мог корректно это отразить.

selectedTrack

Сделаем подсветку определенного трека двумя способами.

Вариант 1

В каждый объект трека добавляется булевое поле isSelected: true/false. Алгоритм рендеринга проверяет это свойство и применяет, например, CSS-стиль выделения(добавляем border выбранному элементу). Проверяем наличие свойства в объекте с помощью тернарного выражения.

📌

Тернарный оператор — это краткая форма записи условного выражения if...else, позволяющая выполнить одно из двух действий в зависимости от истинности условия. Если условие верно, то выполняется выражение слева от :, если ложно, то выполняется правое от : выражение.

ternaryOperator

🔗Тернарный оператор

App.tsx
//...
const tracks = [
  {
    id: 1,
    isSelected: false,
    title: "Musicfun soundtrack",
    url: "https://musicfun.it-incubator.app/api/samurai-way-soundtrack.mp3",
  },
  {
    id: 2,
    isSelected: true,
    title: "Musicfun soundtrack instrumental",
    url: " https://musicfun.it-incubator.app/api/samurai-way-soundtrack-instrumental.mp3",
  },
]
 
function App() {
  //...
  return (
    <>
      <h1>Musicfun player</h1>
      <ul>
        {tracks.map((track) => (
          <li key={track.id} style={{ border: track.isSelected ? "1px solid orange" : "none" }}>
            <div>{track.title}</div>
            <audio controls src={track.url}></audio>
          </li>
        ))}
      </ul>
    </>
  )
}
 
export default App

Плюсы: просто, если с backend данные уже приходят с этим флагом.

❌ Минусы:

  • Возможна несогласованность (например, выделено несколько треков, хотя должен быть один).
  • Засоряет данные frontend-логикой, которая необходима только для UI.

Вариант 2

Можно создать отдельную переменную selectedTrackId, в которой будет храниться id выбранного трека или null, если ничего не выбрано. Алгоритм проходит по списку треков и для каждого сравнивает track.id === selectedTrackId. Если совпадает — применяется стиль выделения. Если selectedTrackId=== null → выделений нет:

App.tsx
//...
const tracks = [
  {
    id: 1,
    title: "Musicfun soundtrack",
    url: "https://musicfun.it-incubator.app/api/samurai-way-soundtrack.mp3",
  },
  {
    id: 2,
    title: "Musicfun soundtrack instrumental",
    url: " https://musicfun.it-incubator.app/api/samurai-way-soundtrack-instrumental.mp3",
  },
]
const selectedTrackId = 1
 
function App() {
  //...
  return (
    <>
      <h1>Musicfun player</h1>
      <ul>
        {tracks.map((track) => (
          <li
            key={track.id}
            style={{ border: track.id === selectedTrackId ? "1px solid orange" : "none" }}
          >
            <div>{track.title}</div>
            <audio controls src={track.url}></audio>
          </li>
        ))}
      </ul>
    </>
  )
}
//...

✅ Преимущества:

  • Нормализация: данные треков остаются «чистыми», логика выделения вынесена отдельно.

  • Согласованность: изменения в одном месте, без риска забыть обновить данные в разных структурах.

  • Гибкость: удобно управлять выделением, даже если сервер не предоставляет эту информацию.

Итог занятия

✔️ Основная идея заключается в том, что данные всегда являются фундаментом, но их проектирование должно идти в связке с алгоритмом.

✔️ При разработке важно уметь различать разные состояния приложения: момент загрузки, ситуацию с пустым списком и работу с уже загруженными элементами.

✔️ Для интерактивности предпочтительно хранить отдельные переменные (например, ID), а не добавлять лишние поля в сами данные.

✔️ Согласованность состояния — ключ к правильной и предсказуемой работе интерфейса.

App.tsx
// const tracks = []
// const tracks = null
const tracks = [
  {
    id: 1,
    title: "Musicfun soundtrack",
    url: "https://musicfun.it-incubator.app/api/samurai-way-soundtrack.mp3",
  },
  {
    id: 2,
    title: "Musicfun soundtrack instrumental",
    url: " https://musicfun.it-incubator.app/api/samurai-way-soundtrack-instrumental.mp3",
  },
]
const selectedTrackId = 1
 
function App() {
  if (tracks === null) {
    return (
      <div>
        <h1>Musicfun player</h1>
        <span>Loading...</span>
      </div>
    )
  }
 
  if (tracks.length === 0) {
    return (
      <div>
        <h1>Musicfun player</h1>
        <span>No tracks</span>
      </div>
    )
  }
 
  return (
    <>
      <h1>Musicfun player</h1>
      <ul>
        {tracks.map((track) => (
          <li
            key={track.id}
            style={{ border: track.id === selectedTrackId ? "1px solid orange" : "none" }}
          >
            <div>{track.title}</div>
            <audio controls src={track.url}></audio>
          </li>
        ))}
      </ul>
    </>
  )
}
export default App

🏠 Домашнее задание

Алгоритмы и структуры данных в React

Цель задания

Закрепить понимание взаимосвязи между структурами данных и алгоритмами рендера в React на примере создания интерактивного каталога товаров.

Задание 1

Базовая структура данных и алгоритм рендера

Создайте новый React-проект с помощью Vite либо можете продолжать работу в проекте из предыдущих домашних заданий.

В App.tsx, отрисуйте список задач

App.tsx
const tasks = [
  {
    id: 1,
    title: "Купить продукты на неделю",
    isDone: false,
    addedAt: "1 сентября",
  },
  {
    id: 2,
    title: "Полить цветы",
    isDone: true,
    addedAt: "2 сентября",
  },
  {
    id: 3,
    title: "Сходить на тренировку",
    isDone: false,
    addedAt: "3 сентября",
  },
]
 
export function App() {
  /* Ваш код */
}

Итоговый результат 🚀

💡
Стили можете сделать на свое усмотрение

les8-1

Задание 2

Отображение состояний загрузки

Напишите алгоритм, который будет соответствовать следующим условиям:

  1. Если tasks = null → показать "Загрузка..."

les8-3

  1. Если tasks = [] → показать "Задачи отсутствуют"

les8-2

  1. Если массив содержит таски → отрендерить список

les8-1

Задание 3

Статус выполнения задачи

В зависимости от свойства isDone добавьте стили для заголовка задачи.

  • Если задача выполнена, тогда необходимо зачеркнуть заголовок задачи. textDecorationLine: line-through
  • Если задача не выполнена, тогда оставляем как есть. textDecorationLine: none

Итоговый результат 🚀

les8-zad-3

Задание 4

Приоритет таски

В массиве сейчас 3 задачи. Но у каждой из задач может быть разный приоритет выполнения. Поэтому расширим данные свойством priority.

  • 0 - Низкий приоритет
  • 1 - Средний приоритет
  • 2 - Высокий приоритет
  • 3 - Срочный приоритет
  • 4 - Наивысший приоритет

Чтобы пользователь видел задачи с высоким приоритетом (2,3,4) добавьте свойство priority в каждую таску и измените background для данных задач на оранжевый

App.tsx
const tasks = [
  {
    id: 1,
    title: "Купить продукты на неделю",
    isDone: false,
    addedAt: "1 сентября",
    priority: 2,
  },
  {
    id: 2,
    title: "Полить цветы",
    isDone: true,
    addedAt: "2 сентября",
    priority: 0,
  },
  {
    id: 3,
    title: "Сходить на тренировку",
    isDone: false,
    addedAt: "3 сентября",
    priority: 1,
  },
]

Итоговый результат 🚀

les8-zad-4

Задание 5 ⭐

⭐ Дополнительное задание со звездочкой. Проделывать по желанию.

Для каждой задачи сделайте свой цвет.

  • 0 - Низкий приоритет (#ffffff)
  • 1 - Средний приоритет (#ffd7b5)
  • 2 - Высокий приоритет (#ffb38a)
  • 3 - Срочный приоритет (#ff9248)
  • 4 - Наивысший приоритет (#ff6700)

Доработайте код таким образом, чтобы цвет фона менялся в зависимости от приоритета задачи.

App.tsx
const tasks = [
  {
    id: 1,
    title: "Купить продукты на неделю",
    isDone: false,
    addedAt: "1 сентября",
    priority: 2,
  },
  {
    id: 2,
    title: "Полить цветы",
    isDone: true,
    addedAt: "2 сентября",
    priority: 0,
  },
  {
    id: 3,
    title: "Сходить на тренировку",
    isDone: false,
    addedAt: "3 сентября",
    priority: 1,
  },
  {
    id: 4,
    title: "Срочно отправить рабочий отчет",
    isDone: false,
    addedAt: "4 сентября",
    priority: 4,
  },
  {
    id: 5,
    title: "Заплатить за коммунальные услуги",
    isDone: false,
    addedAt: "3 сентября",
    priority: 3,
  },
]

Итоговый результат 🚀

les8-zad-5


💡

Совет: Если что-то кажется сложным, не переживайте! Это нормально на начальном этапе. Главное — это практика и постепенное понимание концепций React.

Боевой маршрут (React Путь Самурая: без альтернатив)

Видеоурок - 9 видео из 30