Скидываю сюда чтоб ссылка не потерялась. Ну и как пример кода в соответствии с требуемым стеком (см. описание). От себя добавил пару фич для удобства навигации и возврату к статье из списка.
Изначальная постановка задачи
Реализовать веб-страницу для простора статей ресурса Hacker News с использованием их публичного API
Требования при постановке:
- Технологии: React / Redux Toolkit / Material UI / React Router;
- Страницы: 1) Общий список статей; 2) Каждая из статей + Под контентом должны отображаться комментарии в виде дерева (корневые комментарии грузятся сразу, по клику на комментарий подгружаются его дочерние комментарии следующего уровня);
Стек
- Vite / React / TypeScript
- Redux Toolkit
- MUI / CSS modules
- Web Workers
- LocalStorage
Из интересного и нетривиального:
- Web Workers для загрузок основного списка из 500 элементов (+настраиваемое время жизни кэша внутри).
Главная идея такого подхода состоит из двух целей: 1) Вынести очередь запросов в отдельный поток воркера; 2) При переходах клиента по урлам очередь запросов продолжается пока пользователем не будет затребован другой список.
Из минусов (в силу ограничения по времени): код для воркеров без типизации и он не участвует в сборке, он он написан так, чтоб сам себя тестировал. В планах написать бойлерплейт на TypeScript две версии - под клиентский Vite и серверный Next.js (вернее, корректное создание и удаление воркера на клиенте для тяжёлых вычислений). Воркер получает массив из id и начинает очередь запросов в своем (изолированном) потоке, если пользователь хочет получить другой список - очередь прерывается, создаётся новая.
- MUI (из требований по ТЗ)
- PollingComponent для обновления каждого комментария (я подумал, это было бы логично и в духе ТЗ, т.к. он содержит обновленный список id дочерних комментариев) - механизм может быть настроен на отключение поллинга при первом успешном ответе)
Roadmap (сопутствующие второстепенные задачи):
- ✅ Оптимизировать размеры бандла
Конфиг для примера (сравнение ДО и ПОСЛЕ на скринах):
const modulesToSeparate = [
'axios',
'retry-axios',
'@mui/material',
'@remix-run',
'react-dom',
]
See also vite config diffs
- ⏸️ Те запросы что в основном потоке можно реализовать в динамически создаваемых воркерах (например, как я это реализовал 3 года назад здесь). Что сейчас - Поллинг в основном потоке для:
- Основного списка для очереди (раз в 60 сек)
- Активной статьи и каждого комментария к ней (раз в 60 сек)
- ✅ Добавить тесты (прикрутить Vitest) target diffs
- ✅ Рефакторинг компонента 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" },
]}
/>
);
};
- ✅ Контекст MainNewsListUpdateLogic как отдельный слой бизнес-логики просто чтоб сделать компонент App чище
- ✅ Интересная фича - favicon может быть перерисован, как это сделано в почте у Яндекса, например. Нашел вариант на чистом js, переписал утилиту documentFaviconBadger на TypeScript
Дополнительные фичи (чего лично мне не хватило как пользователю):
- ✅ Режимы основного списка - реализовано с отменой существующей очереди при смене режима. Таким образом, приложение практически не делает лишних запросов (теоретически возможен редкий кейс: когда запрос не успеет отмениться из-за погрешности на время обновления стейта ключа в реакте + есть несущественная погрешность на время прибытия события от клиента в воркер и обратно - за это время что-то лишнее может быть получено от воркера, но будет проигнорировано клиентом)
- ✅ Возможность сохранять избранное в локальном хранилище
See also https://hnpwa.com/