Скидываю сюда чтоб ссылка не потерялась. Ну и как пример кода в соответствии с требуемым стеком (см. описание). От себя добавил пару фич для удобства навигации и возврату к статье из списка.

Изначальная постановка задачи

Реализовать веб-страницу для простора статей ресурса Hacker News с использованием их публичного API

Требования при постановке:

  • Технологии: React / Redux Toolkit / Material UI / React Router;
  • Страницы: 1) Общий список статей; 2) Каждая из статей + Под контентом должны отображаться комментарии в виде дерева (корневые комментарии грузятся сразу, по клику на комментарий подгружаются его дочерние комментарии следующего уровня);

Стек

  • Vite / React / TypeScript
  • Redux Toolkit
  • MUI / CSS modules
  • Web Workers
  • LocalStorage

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

  1. Web Workers для загрузок основного списка из 500 элементов (+настраиваемое время жизни кэша внутри).

Главная идея такого подхода состоит из двух целей: 1) Вынести очередь запросов в отдельный поток воркера; 2) При переходах клиента по урлам очередь запросов продолжается пока пользователем не будет затребован другой список.

Из минусов (в силу ограничения по времени): код для воркеров без типизации и он не участвует в сборке, он он написан так, чтоб сам себя тестировал. В планах написать бойлерплейт на TypeScript две версии - под клиентский Vite и серверный Next.js (вернее, корректное создание и удаление воркера на клиенте для тяжёлых вычислений). Воркер получает массив из id и начинает очередь запросов в своем (изолированном) потоке, если пользователь хочет получить другой список - очередь прерывается, создаётся новая.

  1. MUI (из требований по ТЗ)
  2. PollingComponent для обновления каждого комментария (я подумал, это было бы логично и в духе ТЗ, т.к. он содержит обновленный список id дочерних комментариев) - механизм может быть настроен на отключение поллинга при первом успешном ответе)

Roadmap (сопутствующие второстепенные задачи):

  1. ✅ Оптимизировать размеры бандла

Конфиг для примера (сравнение ДО и ПОСЛЕ на скринах):

const modulesToSeparate = [
  'axios',
  'retry-axios',
  '@mui/material',
  '@remix-run',
  'react-dom',
]

See also vite config diffs

  1. ⏸️ Те запросы что в основном потоке можно реализовать в динамически создаваемых воркерах (например, как я это реализовал 3 года назад здесь). Что сейчас - Поллинг в основном потоке для:
  • Основного списка для очереди (раз в 60 сек)
  • Активной статьи и каждого комментария к ней (раз в 60 сек)
  1. ✅ Добавить тесты (прикрутить Vitest) target diffs
  2. Рефакторинг компонента PollingComponent - теперь без any TS код стал более предсказуемым, но (кмк) не лучше читабельным (об этом не раз видел комменты вроде этого - отчасти согласен с ним). target diffs

NOTE: 1

const genericMemo: <T>(component: T) => T = memo

function GenericComponent<Value>(props: TPropsWithGeneric<Value>) {
  return <div />
}

export const GenericMemoComponent = genericMemo(GenericComponent)

NOTE: 2

function MyComponent<T>(props: {
  options: {
    label: string;
    value: T;
  }[];
}) {
  return <div>MyComponent</div>;
}

type TDominantHand = "RIGHT" | "LEFT";

function ParentComponent() {
  return (
    <MyComponent<TDominantHand>
      options={[
        { label: "Right Hand", value: "RIGHT" },
        { label: "Left Hand", value: "LEFT" },
      ]}
    />
  );
};
  1. ✅ Контекст MainNewsListUpdateLogic как отдельный слой бизнес-логики просто чтоб сделать компонент App чище
  2. ✅ Интересная фича - favicon может быть перерисован, как это сделано в почте у Яндекса, например. Нашел вариант на чистом js, переписал утилиту documentFaviconBadger на TypeScript

Дополнительные фичи (чего лично мне не хватило как пользователю):

  1. Режимы основного списка - реализовано с отменой существующей очереди при смене режима. Таким образом, приложение практически не делает лишних запросов (теоретически возможен редкий кейс: когда запрос не успеет отмениться из-за погрешности на время обновления стейта ключа в реакте + есть несущественная погрешность на время прибытия события от клиента в воркер и обратно - за это время что-то лишнее может быть получено от воркера, но будет проигнорировано клиентом)
  2. ✅ Возможность сохранять избранное в локальном хранилище

See also https://hnpwa.com/