Полка React и Next: данные, состояние, формы и адрес
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
Снимок: сентябрь 2026. Своими руками в этой части собраны кеш промисов, контекст с редьюсером, форма на action и файловый маршрутизатор. У каждой миниатюры есть стена — место, где своего кода становится больше, чем самой задачи. Здесь — где именно она проходит и какой пакет берут за ней. Версии и веса на полке — снимок на дату проверки, а не вечная истина; как проверить строку самому, разбирает статья «Живой ли пакет».
Полка устроена так же, как вся часть: сначала то, что умеют React и Next, и только за стеной — пакет. У первых восьми строк таблицы ниже есть паспорт: когда пакет не нужен, что даёт платформа, где в учебнике своя сборка, снимок версий с датой проверки и условие, при котором строку пора пересмотреть. Остальные строки паспорта не получили — они показывают, куда смотреть.
| Задача | Сначала | Если мало |
|---|---|---|
| данные и кеш запросов | серверный компонент; use() с кешем по ключу | TanStack Query |
| состояние клиента | useReducer и контекст | Zustand |
| формы | action, useActionState, FormData | React Hook Form, схема через Standard Schema |
| маршруты вне Next | свой маршрутизатор на два-три экрана | React Router или TanStack Router |
| переводы | Intl.PluralRules и Intl.NumberFormat | next-intl |
| состояние в адресе | searchParams и useSearchParams | nuqs |
| картинки | next/image | ничего не ставить |
| защита действий | схема и сессия прямо в действии | next-safe-action |
| тесты компонентов | console.assert и мысль о фреймворке — «Тест-фреймворки» | Testing Library поверх Vitest; паспорта нет, enzyme сошёл с полки |
| границы ошибок | свой класс-граница — «Почему это до сих пор класс» | react-error-boundary — его называет та же статья |
| анимация | уход через element.animate и фазу присутствия, View Transitions — пока Newly; то и другое в «Анимации в React» | Motion — разобран там же |
| компоненты без стилей | <dialog>, popover и <details> — что пока требует скрипта | Base UI или React Aria — причина в разделе «Не на полке» у shadcn/ui |
| ошибки и аналитика | error.tsx ловит ошибку на экране — раздел о файле | как стек из прода превращается в имена, разобрано в части V — «Стек с телефона»; у облачного сервиса с идентификаторами пользователей та же оговорка, что у Clerk |
В таблицах-снимках ниже есть столбец «Вес или цена». Килобайт там — 1024 байта после gzip: типичный импорт собран esbuild в один файл с минификацией, react и react-dom не считаются — они в проекте уже есть. Что сборщик делает с импортами по дороге, разбирает глава «Сборка», начиная со статьи «Свой сборщик». «Типичный» здесь главное слово: другой набор импортов даёт другой вес, поэтому в скобках записано, что именно импортировали. Где вес не мерили, так и сказано, и названа цена другого рода.
Строка полки стареет не по календарю, а по событию: вышел мажор, пакет пометили устаревшим, платформа догнала. Поэтому у каждого паспорта своё «Пересмотреть, если…».
Прежде чем открывать паспорта, ответьте на один вопрос: где в приложении живут данные. Серверный компонент читает их до браузера — await прямо в теле компонента, — и в клиент едет готовая разметка, а не программа, которая эти данные добудет. Два одинаковых запроса за один ответ склеивает карта начатых промисов, то есть cache() на сервере («Один и тот же запрос из двух мест»). Живёт эта карта ровно один ответ, а в браузерной сборке React cache() и вовсе пропускает вызов насквозь.
Отсюда раскладка полки. Библиотека данных нужна там, где запрос уходит из браузера: живой поиск, лента, которая подгружается при прокрутке, экран, который обновляется без перезагрузки. Если страницы читают данные на сервере, а меняют их действия, клиентский кеш запросов повторял бы работу, уже сделанную до браузера.
Вход, база и очереди в этом учебнике живут вообще не в Next, а в отдельном API части VI: там таблицы описывает схема Drizzle, а личность проверяется на каждом запросе. Поэтому Better Auth и Drizzle на этой полке паспортов не получают: их место — рядом с API.
Вопрос «где сервер» задают до выбора пакета. Ответ «данные читает серверный компонент» вычёркивает из списка кандидатов больше, чем любое сравнение библиотек.
Не нужна, если данные читает серверный компонент или хватает use() с кешем по ключу. Платформа: fetch — Widely, а ждать промис умеет сам React 19 — use() и граница Suspense.
Своя сборка в учебнике двойная. Запрос в эффекте с флагом ignore собран в «Запросе целиком», и там же честно сказано, чего в нём нет: кеша, повтора при обрыве и склейки одинаковых запросов от двух компонентов. Карта промисов для use() — в «Промис нельзя создавать в теле». Проверим обе на одной задаче: шапка и боковая панель показывают одну и ту же задачу. Прогноз: сколько запросов уйдёт на сервер?
useTask — свой эффект со своим запросом: Header и Sidebar ничего друг о друге не знают.Свой эффект на каждый вызов хука означает свой запрос на каждый компонент, который читает запись. На двух компонентах это незаметно, на двадцати — двадцать одинаковых запросов при каждом открытии экрана.
Карта по ключу склеивает их: второй читатель получает не новый запрос, а тот же начатый промис. Усложним сценарий. Пока экран открыт, задачу поправили на сервере, и приложение перерисовалось. Прогноз: что будет на экране?
Карта закрывает склейку — и тут же показывает, чего в ней нет. Это и есть стена. Вопрос уже не «как кешировать», а «кто, когда и по какому событию удаляет ключ из карты»: после своей правки, при возврате в окно, по истечении срока, при ошибке — с повтором или без. Каждое такое правило — ещё десяток строк и ещё одна гонка. Вместе они и составляют TanStack Query.
Тот же сценарий на настоящем ядре TanStack Query — в Node, без React: два одновременных fetchQuery, правка на сервере, чтение из кеша и invalidateQueries.
Первые две строки повторяют нашу карту: один запрос на двоих и старое значение из кеша. Третья — то, чего у карты нет: invalidateQueries помечает запись по ключу устаревшей, и следующее чтение снова идёт на сервер. Внутри @tanstack/react-query то же ядро, а useQuery подписывает компонент на запись в нём.
На сервере Next та же идея выглядит иначе: не ключ запроса, а метка, под которой копятся ключи, — «Свой кеш: ключ из входа, метка вместо списка адресов». Если данные читает сервер, сбрасывает их revalidateTag, и клиентской библиотеке здесь делать нечего.
| Библиотека | Версия | Вес или цена | Проверено |
|---|---|---|---|
@tanstack/react-query | 5.102.8 | 9,7 КБ без react (QueryClient + Provider + useQuery) | 14.09.2026 |
swr | 2.5.1 | 6,2 КБ без react (useSWR) | 14.09.2026 |
На полке TanStack Query; SWR — запас: тот же подход «показать, что есть, и перепроверить» при меньшем весе.
Пересмотреть, если TanStack Query выпустит v6 или React даст клиентский кеш запросов с инвалидацией.
Не нужна, если хватает useReducer и контекста. Платформы у этой задачи нет, есть только React: состояние живёт в компоненте наверху, контекст раздаёт его вниз, а для источников вне React есть useSyncExternalStore.
Своя сборка: когда редьюсер честнее useState — таблица признаков; кто перерисуется, когда значение контекста сменилось, — проверка печатью каждого рендера; подписка на внешний источник — фолд про useSyncExternalStore в статье «Хук или просто функция».
Стена контекста проявляется, когда в одном значении лежат независимые куски. Соберём оба варианта рядом: контекст с объектом { count, theme } и хранилище вне React, на которое компоненты подписываются через useSyncExternalStore с селектором. Меняется только count. Прогноз: сколько раз отрисуется компонент, который показывает тему, в каждом из вариантов?
value — новое значение для всех читателей. У хранилища тема отрисовалась один раз: React сравнил то, что вернул селектор, с прошлым результатом, получил ту же строку "светлая" и компонент не тронул.Значение контекста сравнивается по ссылке, поэтому новый объект перерисовывает всех читателей разом. Результат селектора сравнивается по содержимому, поэтому читатель нетронутого кусочка стоит на месте. Это и приносит Zustand: хранилище вне дерева, подписка на кусочек состояния и никаких провайдеров.
У селектора есть своя ловушка, и её стоит знать до первого проекта. Если селектор возвращает новый объект — (s) => ({ count: s.count }), — снимок при каждом чтении отличается от прошлого: React перерисовывает, снова читает, снова получает новый объект и уходит в бесконечный цикл. Примера на это здесь нет — цикл роняет его. У Zustand для такого селектора есть useShallow: он сравнивает поля объекта, а не ссылку.
Настоящий Zustand, ядро без React: подписчик получает новое и прежнее состояние и сам решает, есть ли ему дело до перемены.
Хук, который возвращает create из zustand, делает то же сравнение за компонент — через селектор, как наш useStore.
| Библиотека | Версия | Вес или цена | Проверено |
|---|---|---|---|
zustand | 5.0.15 | 0,4 КБ без react (create) | 14.09.2026 |
jotai | 3.0.0 | 3,1 КБ без react (atom + useAtom) | 14.09.2026 |
@reduxjs/toolkit | 2.12.0 | 8,4 КБ (configureStore + createSlice) | 14.09.2026 |
На полке Zustand. Jotai — запас, если состояние удобнее держать отдельными атомами, а не одним объектом; его мажор 3.0.0 вышел 08.09.2026, за неделю до снимка, и выпусков исправлений у него пока нет. Redux Toolkit — запас для больших команд и старого кода: если проект уже на Redux, живая точка входа — он, а не ядро (почему — в разделе «Не на полке»). Zustand и TanStack Query — чистый JavaScript, поэтому оба стоят и на полке React Native.
Пересмотреть, если Jotai 3 наберёт выпусков исправлений или Zustand выпустит v6.
Не нужна для простых форм: action, useActionState и FormData. Платформа держит свою половину — встроенная проверка формы (required, pattern) Widely, — а React 19 берёт остальное: функция в action получает готовый FormData, а useActionState возвращает состояние после отправки.
Разбор входа тоже собран руками: одна функция на границе, на выходе значение или причина отказа («Разобрать, а не поверить», там же фолд про библиотеку разбора), и тот же принцип для любых внешних данных — «Когда данные не подошли».
Стена у форм двойная. Первая — объём: десятки полей, проверка на лету, зависимые поля, повторяющиеся группы строк. Тогда форму отдают React Hook Form или TanStack Form. Вторая — разбор: схема, написанная один раз, должна работать и в форме, и в действии, и потом на сервере API. Для второй стены у экосистемы есть общий интерфейс.
Standard Schema — не библиотека, а договор из одного свойства. Схема любой библиотеки, которая его поддерживает, кладёт в поле "~standard" объект: version, vendor с именем библиотеки, функцию validate и необязательные types для вывода типа. Потребитель — форма, действие, проверка на сервере — знает только этот объект и не импортирует ни zod, ни valibot. Одна деталь договора легко теряется: элемент пути до ошибки бывает либо ключом, либо объектом { key }, и потребитель обязан понимать оба.
Соберём потребителя и две самодельные «библиотеки» с разным видом пути. Прогноз: что напечатает пример?
Все три строки прошли через parseBody, а она ни одной «библиотеки» не импортирует. Первая — удачный разбор: схема Б заодно обрезала пробелы, а у task нет своей аннотации — тип выведен из объявления схемы, поэтому toUpperCase компилятор пропускает. Две другие — отказы, и в них главная строчка: typeof seg === "object" ? seg.key : seg. Без неё отказ схемы Б напечатал бы вместо имени поля [object Object].
Это не выдумка ради примера. У настоящих zod и valibot путь выглядит так же по-разному; объект valibot для печати свёрнут в {key:title}, сам он больше — там ещё тип, вход и значение.
Кто читает интерфейс — видно по реестру. @hookform/resolvers 5.9.1 экспортирует вход ./standard-schema и держит @standard-schema/spec в peerDependencies. Это поле впервые встречается на полке, поэтому одной фразой: peerDependencies — «работаю рядом с таким-то пакетом, но в себе его не везу»; подробнее — в статье «Живой ли пакет», в вопросе о доле в нише. TanStack Form принимает Standard Schema сам, без резолвера: в его ядре есть свой валидатор под этот интерфейс. Сама спецификация — пакет из одних типов, в сборку он не попадает. Серверный валидатор с выводом типа соберём в части VI — «Собираем валидатор границы».
| Библиотека | Версия | Вес или цена | Проверено |
|---|---|---|---|
react-hook-form | 7.88.0 | 10,8 КБ без react (useForm) | 14.09.2026 |
@hookform/resolvers | 5.9.1 | вход ./standard-schema; вес не мерил | 14.09.2026 |
@tanstack/react-form | 1.33.5 | 17,3 КБ без react (useForm) | 14.09.2026 |
@standard-schema/spec | 1.1.0 | только типы, в сборку не попадает | 14.09.2026 |
На полке React Hook Form с резолвером Standard Schema; TanStack Form — запас.
Пересмотреть, если React Hook Form 8 выйдет из беты (тег beta — 8.0.0-beta.3) или появится Standard Schema v2.
Не нужна в Next и Expo Router; для двух-трёх экранов хватит своего. Платформа — History API: pushState и событие popstate, Widely. На них стоял и маршрутизатор из тридцати строк, с которого начиналась глава про Next.js.
Своя сборка: маршруты своими руками — тридцать строк и таблица того, что роутер обязан уметь, — и файловый маршрутизатор целиком, который закрыл половину этой таблицы.
Стена — вторая половина той же таблицы: прокрутка и фокус при переходе, разрезание бандла по экранам, данные до отрисовки, а отдельно — ответ сервера, когда посетитель жмёт F5 на внутреннем адресе. В Next всё это решено файлами, поэтому пакет нужен приложению без Next: одностраничному приложению на Vite, встраиваемому виджету, старому проекту.
| Библиотека | Версия | Вес или цена | Проверено |
|---|---|---|---|
react-router | 8.3.1 | 14,1 КБ без react (BrowserRouter + Routes + Route + Link) | 14.09.2026 |
@tanstack/react-router | 1.170.36 | 27,6 КБ без react (createRouter + один маршрут) | 14.09.2026 |
wouter | 3.11.0 | 2,9 КБ без react (Route + Link + Switch) | 14.09.2026 |
На полке React Router 8. Его мажор 8.0.0 вышел 17.06.2026, пакет только ESM ("type": "module" и ни одного входа для require), а в peerDependencies он требует react и react-dom не ниже 19.2.7 — проект на React 18 сначала обновляет React. TanStack Router — запас, если маршруты, параметры и строка запроса должны быть типизированы целиком. wouter — запас для двух-трёх экранов, когда своя сборка уже написана, а поддерживать её не хочется.
Пересмотреть, если выйдет React Router 9 или TanStack Router 2.
Не нужна для одного языка: хватит Intl.PluralRules и NumberFormat. Платформа: Intl.PluralRules — Widely с 19.03.2022, Intl.NumberFormat — тоже Widely. Своей сборки в учебнике нет, потому что Intl.PluralRules до этой полки не называла ни одна статья; её роль играет пример ниже.
В русском числа делятся на четыре категории: одна задача, три задачи, пять задач — и отдельная категория для дробей, хотя слово в ней то же, что после трёх. Правило, какое число в какую категорию попадает, движок знает сам. Прогноз: какая форма достанется числам 11, 21 и 1.5?
Правил в коде нет: rules.select(n) возвращает категорию — one, few, many или other, — а движок выбирает её по таблицам правил CLDR, общего справочника языков, который браузер везёт с собой. Слово к категории подбираете вы. Словарь на шесть ключей нужен только ради типа: Record<Intl.LDMLPluralRule, string> требует все категории, включая zero и two, которых в русском нет.
Стена — второй язык. Появляются адреса с языком (/en/tasks), словари, которые грузятся на сервере и не должны целиком уезжать в клиент, выбор языка по заголовку запроса — это next-intl, со входами ./routing, ./server и ./navigation. Один вход требует оговорки: он называется ./middleware, но в Next 16 его подключают в файле proxy.ts, а не middleware.ts. Имя входа осталось с тех времён, когда файл назывался по-старому; документация next-intl пишет то же (сверено 14.09.2026).
| Библиотека | Версия | Вес или цена | Проверено |
|---|---|---|---|
next-intl | 4.14.5 | для Next 12–16; вес не мерил (зависит от словарей) | 14.09.2026 |
Пересмотреть, если next-intl выпустит v5 или Next вернёт встроенные переводы в App Router.
Не нужна для одного-двух параметров: searchParams и useSearchParams. Платформа — URL и URLSearchParams, Widely.
Своя сборка: разбор строки запроса — откуда у searchParams тип string | string[] | undefined и почему его сужают руками — и методы useRouter: push добавляет запись в историю, replace заменяет текущую.
Стена наступает, когда в адресе живёт состояние экрана: фильтр, сортировка, номер страницы, строка поиска. Каждый параметр — сужение типа, умолчание, запись обратно в адрес и решение, нужна ли новая запись в истории на каждый введённый символ. Пять параметров — пять копий этого кода. nuqs превращает параметр адреса в подобие useState: тип и умолчание объявляются один раз, а запись в адрес по умолчанию идёт через replace.
| Библиотека | Версия | Вес или цена | Проверено |
|---|---|---|---|
nuqs | 2.10.1 | адаптеры next/react-router/tanstack-router; вес не мерил | 14.09.2026 |
Адаптеры в exports пакета — для Next (App Router и Pages Router), React Router, TanStack Router и обычного React, поэтому nuqs переживает и уход с Next.
Пересмотреть, если выйдет nuqs 3.
Ставить ничего не нужно: next/image, а sharp приезжает с next. Платформа делает свою часть — srcset и loading="lazy" у <img>, оба Widely, — и next/image пишет эти атрибуты за вас, а картинки нужного размера готовит сервер Next.
Кто сжимает их на сервере, видно по реестру:
sharp стоит в optionalDependencies: npm ставит его вместе с next, а если установка не удалась, npm install не падает. Рядом — компиляторы SWC под каждую систему, и из них npm возьмёт только подходящий вашей.
Своей сборки в учебнике нет, потому что сжатие картинок — работа сервера и библиотеки на C (внутри sharp — libvips), а не React. Зато граница уже пройдена: при статическом экспорте next/image с оптимизацией роняет сборку, и нужен images: { unoptimized: true } или свой loader — сжимать картинку в момент запроса некому. Доска задач из проекта этой части экспорт не пережила бы и без картинок: её формы сделаны действиями — это разобрано в «Экспорт или Node-сервер». Свой sharp в зависимостях нужен только для собственной обработки загрузок на сервере, и это уже часть VI.
| Библиотека | Версия | Вес или цена | Проверено |
|---|---|---|---|
next | 16.3.5 | next/image встроен | 14.09.2026 |
sharp | 0.35.4 | приезжает с next, отдельно не ставить | 14.09.2026 |
Пересмотреть, если sharp уйдёт из optionalDependencies next.
Не нужна, пока действий немного: схема и сессия прямо в действии. Платформа здесь — сам Next: действие — функция на сервере, и всё, что ей нужно проверить, проверяется в ней.
Своя сборка: каркас действия из четырёх шагов — личность, разбор входа, право, работа — и личность из сессии, а не из полей формы.
Стена — повтор. Когда действий десятки, первые шаги копируются в каждое: достать пользователя, разобрать вход схемой, вернуть отказ в одном и том же виде. next-safe-action собирает из этих шагов клиент действий: функции, добавленные его методом use() (с use() из React у этого метода общее только имя), выполняются перед каждым действием, а схема входа объявляется рядом с ним и читается через Standard Schema. В документации библиотеки такие функции называются middleware, и к файлу proxy.ts из Next 16, бывшему middleware.ts, они отношения не имеют: тот стоит перед маршрутами и видит запрос, а цепочка библиотеки работает внутри вызова действия.
| Библиотека | Версия | Вес или цена | Проверено |
|---|---|---|---|
next-safe-action | 8.7.3 | один сопровождающий; вес не мерил | 14.09.2026 |
У пакета один сопровождающий в реестре — это пометка о риске, а не отказ. Сами проверки при этом остаются вашими функциями и вашей схемой, а библиотека только выстраивает их в цепочку: такая зависимость меняется на границе, если с ней что-то случится.
Пересмотреть, если next-safe-action выпустит v9 или в Next появится встроенная цепочка для действий.
Эти пакеты ещё встречаются в ответах на форумах, курсах и старых проектах. У каждого — дата последнего выпуска, номер вопроса из «Живой ли пакет», на котором он отсеялся, и что брать вместо. Напомним номера: 0 — не умеет ли этого платформа, 1 — жив ли пакет, 2 — растёт ли его доля в нише.
| Пакет | Последний выпуск | Вопрос | Что вместо |
|---|---|---|---|
Formik, formik | 2.4.9 от 10.11.2025 | 2: за год два патча с разницей в три дня, загрузки за неделю — ×1,02 к той же неделе 2025 года при медиане популярных пакетов ×2,69 | action формы или React Hook Form — раздел «Формы и Standard Schema» |
Create React App, react-scripts | 5.0.1 от 12.04.2022 | 1: команда React объявила его устаревшим для новых приложений 14.02.2025 | сборщик выбирает платформа — «Какой сборщик нужен» из главы «Сборка» |
enzyme | 3.11.0 от 20.12.2019 | 1: без выпусков с конца 2019 года | Testing Library — строка «тесты компонентов» в начале полки |
react-helmet | 6.1.0 от 08.06.2020 | 0 и 1: в Next заголовок и метатеги пишет объект metadata, React 19 и без Next поднимает <title> из компонента в <head>, а выпусков у пакета нет больше шести лет | export const metadata |
react-beautiful-dnd | 13.1.1 от 30.08.2022 | 1: пометка deprecated в реестре, репозиторий в архиве | пометка ведёт на issue #2672 от 14.10.2024, где Atlassian называет свою замену — Pragmatic drag and drop; паспорта перетаскивания на полке нет |
Lucia, lucia | 3.2.2 от 20.10.2024 | 1: пометка deprecated, сообщение ведёт на страницу миграции lucia-auth.com/lucia-v3/migrate | свои сессии — часть VI |
Две строки стоит прочитать внимательнее. Formik жив по датам — вопрос 1 он проходит, — но его ниша уходит: весь реестр растёт быстрее. А react-helmet отсеян дважды: задачу, ради которой его ставили, закрыл фреймворк, и тогда уже неважно, что пакет давно молчит.
Сошедший пакет не обязательно выкорчёвывать завтра. Но новый код на нём не пишут, а при следующей большой правке экрана его меняют.
Пакеты ниже не помечены устаревшими, и у всех, кроме ядра Redux, выпуски свежие. Отказ — не оценка качества, а вопрос 4 из «Живой ли пакет», навязывает ли пакет устройство проекта, либо проверяемый факт из реестра. Честная оговорка: вопрос 4 совпадает с укладом автора учебника — стили здесь пишутся CSS-модулями, — поэтому у каждой строки названа причина, которую можно проверить.
| Пакет | Что проверено | Почему не на полке |
|---|---|---|
| shadcn/ui | CLI shadcn 4.21.0 от 04.09.2026 | вопрос 4: генератор копирует код компонентов в проект и приносит Tailwind. Если нужны доступные примитивы — Base UI (@base-ui/react 1.8.0) или React Aria Components (react-aria-components 1.21.1): своих стилей у них нет, состояние выставлено data-атрибутами вроде data-pressed, и CSS-модуль стилизует их обычным селектором по атрибуту |
| Tailwind CSS | разбирается на полке браузера | вопрос 4: стили переезжают в разметку классами |
| MUI | @mui/material 9.4.0 | вопрос 4: своя система стилей — в peerDependencies @emotion/react и @emotion/styled, — и вид компонентов меняют через неё |
| Redux | ядро redux 5.0.1 от 23.12.2023 | не вопрос 4, а реестр: в типах самого redux 5.0.1 createStore помечен @deprecated с советом брать configureStore из @reduxjs/toolkit, а ядро с 23.12.2023 не выпускалось. Если Redux нужен, это Redux Toolkit — он в «Состоянии» запасом; первым там Zustand, потому что стену контекста закрывает хранилище с селектором, без редьюсеров на всё приложение |
| Prisma | тег latest — 8.0.0-rc.15 | вопрос 4: отдельный язык схемы и шаг генерации клиента. И ловушка, найденная при проверке: тег latest на 14.09.2026 указывает на предварительную версию, так что npm install prisma без диапазона принесёт её. Место этому разговору — часть VI |
| Apollo Client | @apollo/client 4.3.0 | вопрос 4: GraphQL становится моделью данных всего клиента, в peerDependencies — graphql |
| MobX | mobx 7.0.3 | вопрос 4: изменяемые наблюдаемые объекты вместо неизменяемого состояния, на котором стоит вся часть IV |
| Clerk | @clerk/nextjs 7.9.2 | вопрос 4: пользователи и сессии живут на сервере Clerk, и переезд с него — миграция пользователей, а не замена пакета. Где сервис хранит данные пользователей — ещё и юридический вопрос; с 01.07.2025 действует Федеральный закон от 28.02.2025 № 23-ФЗ, учебник его не решает |
Строка Redux отличается от остальных не случайно. Вопрос 4 к Redux Toolkit не применяется: хранилище с редьюсерами — то же устройство, что useReducer с контекстом из этой части, только вне дерева. Отказ ядру — не вкус, а слова его же авторов в его же типах.
Причина «вопрос 4» не запрещает пакет в чужом проекте. Она говорит, что сменить его позже будет стоить переписывания, и это нужно знать до установки, а не после.
- Сначала вопрос «где живёт сервер»: данные, которые читает серверный компонент, и правки через действия вычёркивают половину полки ещё до сравнения библиотек.
- Карта промисов склеивает одинаковые запросы, но не знает, когда ключ устарел. Правила инвалидации — после правки, по фокусу, по сроку, при ошибке — и составляют TanStack Query.
- Новый объект в
valueконтекста перерисовывает всех читателей; хранилище с селектором черезuseSyncExternalStore— только тех, чей кусочек изменился. Это место Zustand, а селектор, который возвращает новый объект, уводит подписку в бесконечный цикл. - Standard Schema — договор из свойства
"~standard": потребитель не знает библиотеку, но обязан понимать оба вида пути до ошибки — ключ и объект{ key }. - Для одного языка хватает
Intl.PluralRules; next-intl нужен со вторым языком и в Next 16 подключается вproxy.ts. nuqs — когда параметров адреса больше пары, а картинкам ставить ничего не нужно:sharpприезжает сnext. - Каждая таблица полки — снимок на 14.09.2026: вес типичного импорта или цена другого рода и условие «Пересмотреть, если…»; сошедшие и отвергнутые пакеты названы с датой и вопросом, на котором они отсеялись.
1/4Шапка и боковая панель читают одну задачу через use() и общую карту промисов. Задачу поправили на сервере, приложение перерисовалось. Что покажет экран?
2/4Контекст хранит объект { count, theme }, и при каждом изменении провайдер кладёт в value новый объект. Меняется только count. Кто из читателей перерисуется?
3/4Потребитель Standard Schema печатает путь до ошибки как issue.path.join("."), не разбирая сегменты. Схема valibot отказала на поле title. Что он напечатает?
4/4Проект на Next 16 подключает next-intl, у которого есть вход ./middleware. В каком файле его подключают?
Кеш, который умеет забывать
Здесь та же карта промисов, что в разделе о данных, и та же правка на сервере. После правки вызывается invalidate(1), но функция пока пустая, и экран показывает старое название. Допишите её так, чтобы после перерисовки на экране было новое название, а запросов на сервер ушло ровно два. Прогноз до правки: что напечатает пример как есть?
Забыть — значит удалить ключ: следующий вызов loadTask(1) не найдёт промис и начнёт новый запрос.
Цена простого решения видна, если заглянуть в рамку сразу после правки: use() получил новый, ещё не разрешённый промис, и граница Suspense на мгновение снова показала «Загрузка…». Библиотека данных поступает мягче: помечает запись устаревшей, оставляет на экране старое значение и подменяет его, когда придёт новое. Это ещё одно правило из тех, что копятся за стеной.
Словарь без лишних форм
Функция plural подбирает форму слова для любого языка, но компилятор не принимает ни один из двух словарей: Record требует все шесть категорий, а в русском нет zero и two, в английском — ещё и few с many. Поменяйте тип Forms так, чтобы обязательной была только категория other, остальные — по желанию, и функция не могла напечатать undefined, если нужной формы в словаре нет. Запустите и прочитайте диагностики до правки.
Все категории делаются необязательными, а other возвращается обязательной пересечением типов:
После Partial выражение forms[rule] имеет тип string | undefined, и ?? forms.other закрывает дыру: у каждого языка категория other есть всегда. Вывод при этом не меняется — английскому хватает двух форм, русскому четырёх. Когда языков станет больше двух, такие словари переезжают в файлы сообщений, и это уже next-intl.
Номер страницы из адреса
Страница получает searchParams, и из него нужно достать номер страницы. Правила: номер — целое число не меньше 1; если параметра нет или в нём мусор — 1; если ключ page пришёл в адресе дважды, берётся первое значение. Сейчас функция просто зовёт Number. Прогноз до правки: сколько из пяти случаев она разберёт правильно?
Сначала сужение трёх вариантов типа, потом проверка числа:
До правки правильным был только первый случай: Number(undefined), Number(["2", "5"]) и Number("abc") дают NaN, а -1 проходит как есть. Теперь вывод — 3 1 2 1 1. Шесть строк на один параметр; на экране с фильтром, сортировкой и поиском таких функций уже четыре, и к каждой — запись обратно в адрес. В nuqs то же самое записывается одним парсером с умолчанием — parseAsInteger.withDefault(1), — хотя проверку «не меньше 1» он за вас не сделает.