Управление параметрами поиска в Next.js с помощью nuqs
Управление параметрами поиска является ключом к созданию динамичных страниц, доступных для общего доступа и закладок. В связи с недавним внедрением Next.js App Router и связанные с ним функции в версиях 13, 14 и 15 обработка параметров поиска, также известных как строки запроса или параметры поиска, и управление состоянием в приложениях React еще никогда не были такими простыми.
Next.js имеет встроенные возможности маршрутизации, но обработка сложных параметров поиска по-прежнему может быть сложной задачей. Независимо от того, создаете ли вы поисковый интерфейс, фильтруете и сортируете контент или управляете сложной навигацией на основе URL-адресов, правильная обработка строк запросов обеспечивает удобство работы с пользователем и позволяет избежать таких проблем, как несогласованные состояния или неработающие URL-адреса.
В этой статье показано, как мы можем использовать nuqs, библиотеку управления состоянием параметров поиска с сохранением типов для Next.js, которая позволяет нам сохранять состояние в URL-адресе, используя параметры поиска.
В этой статье мы будем использовать термины "строки запроса", "параметры запроса", "параметры поиска" и "поисковые параметры" как взаимозаменяемые. Не волнуйтесь, все они означают одно и то же.
Почему важен синтаксический анализ строки запроса
Параметры запроса, или строки запроса, являются частью URL-адреса, который следует за ? персонаж. Они состоят из пар ключ-значение, где ключ и значение разделены символом =. В следующем примере параметрами запроса являются q и pr:
https://www.google.com/search?q=logrocket&pr=1
Строки запросов являются неотъемлемой частью URL-адресов. Они позволяют передавать данные между страницами и приложениями. При правильном анализе и управлении они позволяют:
-
Улучшенный пользовательский опыт благодаря доступным URL-адресам с возможностью добавления в закладки
Улучшенная поисковая оптимизация за счет повышения доступности контента
Упрощенное управление состоянием в сложных приложениях
Расширенные возможности аналитики и отслеживания
Повышенная оперативность работы приложений, особенно при использовании сложных функций поиска или фильтрации
Понимание поведения и предпочтений пользователей
Параметры поиска в URL-адресе обеспечивают глубокую привязку и общие состояния. Однако без надлежащей обработки они могут привести к ухудшению UX, особенно при работе со сложными структурами запросов или несколькими типами данных (например, строками, числами, логическими значениями, массивами). Эти проблемы включают в себя правильное кодирование/декодирование, преобразование типов и поддержание четкой структуры URL-адресов.
Зачем использовать nuqs?
В то время как Next.js обеспечивает встроенную поддержку синтаксического анализа и доступа к строкам запроса через маршрутизатор.запрос, в нем отсутствуют всеобъемлющие функции для управления этими параметрами простым и многократно используемым способом.
nuqs предлагает более гибкий и удобный для разработчиков подход. Он упрощает обработку строк запроса, предоставляя декларативный API, позволяющий пользователям без проблем синхронизировать параметры поиска с React.state.
nuqs полезен тем, что позволяет абстрагироваться от низкоуровневых задач синтаксического анализа, сериализации и управления строками запросов. Он обеспечивает безопасность ввода и поддерживает такие распространенные варианты использования, как установка значений по умолчанию, управление несколькими ключами запроса и обновление параметров URL без перехода со страницы.
особенности nuqs
nuqs поставляется с функциями, которые делают его хорошим выбором для управления параметрами поиска:
-
Типобезопасный: Использует TypeScript для повышения безопасности ввода
Декларативный API: Предлагает интуитивно понятный интерфейс для определения и использования параметров поиска
Поддержка рендеринга на стороне сервера (SSR): Хорошо работает с Next.js Возможностями SSR
Пользовательские сериализаторы: Обеспечивают гибкий анализ и форматирование значений параметров
Поддержка Transition API: Обеспечивает плавную загрузку состояний при обновлении параметров
Встроенный синтаксический анализ: Обеспечивает автоматический разбор параметров поиска в соответствующие типы данных
Синхронизация URL-адресов: Обеспечивает автоматическую синхронизацию между URL-адресом и состоянием вашего приложения
Управление историей: Корректно обрабатывает состояния истории браузера
Преимущества использования nuqs
Использование nuqs в вашем проекте Next.js дает ряд преимуществ:
-
Упрощает управление состоянием за счет сокращения стандартного кода для обработки параметров URL
Улучшает читаемость кода с помощью декларативного API, делая код более интуитивным и простым в обслуживании
Повышает производительность за счет эффективного анализа и обновления параметров поиска
Предлагает лучший опыт разработчика, потому что его тип-безопасности и интеграции с популярными инструментами, как Зод, чтобы улучшить процесс разработки
Могут быть легко интегрированы в Next.js экосистема
Установка и настройка nuqs
Чтобы начать работу с nuqs, сначала инициализируйте новое приложение Next.js используя create-next-app@latest:
â What is your project named? ⦠nuqs-tutorial â Would you like to use TypeScript? ⦠No / Yes â Would you like to use ESLint? ⦠No / Yes â Would you like to use Tailwind CSS? ⦠No / Yes â Would you like to use `src/` directory? ⦠No / Yes â Would you like to use App Router? (recommended) ⦠No / Yes â Would you like to customize the default import alias (@/*)? ⦠No / Yes
Согласно документации nuqs, вы должны выбрать определенную версию библиотеки nuqs для установки в зависимости от версии Next.js, которую вы используете в данный момент:
Если вы используете последнюю версию, перейдите в папку project и установите nuqs, используя приведенную ниже команду:
npm install nuqs@latest # or yarn add nuqs
Теперь перенесите ваши {дочерние элементы} с помощью компонента NuqsAdapter в ваш корневой файл макета:
import { NuqsAdapter } from 'nuqs/adapters/next/app' import { type ReactNode } from 'react' export default function RootLayout({ children }: { children: ReactNode }) { return ( <html> <body> <NuqsAdapter>{children}</NuqsAdapter> </body> </html> ) }
Проще простого! Теперь давайте рассмотрим, как использовать nuqs.
Основное использование nuqs
Я уже создал приложение для составления списка товаров, которое использует Next.JS - useSearchParams для управления параметрами поиска. В этом руководстве мы будем использовать его, чтобы узнать, как nuqs упрощает обработку параметров поиска. Чтобы продолжить, вы можете клонировать репозиторий на GitHub. Там вы увидите определения типов, компоненты, вызовы API и т.д.
Вот как выглядит демонстрационное приложение со списком продуктов:
Чтобы ознакомиться с полными определениями типов, компонентами, вызовами API, интеграцией nuqs и т.д., ознакомьтесь с полным репозиторием здесь.
Для целей этой статьи я буду придерживаться самого необходимого в коде.
Использование перехватчика useQueryState
nuqs позволяет вам управлять состоянием локального пользовательского интерфейса, синхронизируя его с URL-адресом, гарантируя, что параметры поиска будут отображаться в адресной строке браузера. Это становится возможным благодаря предоставлению перехватчика useQueryState, который можно использовать для замены встроенного перехватчика useState в React'е.
Этот перехват принимает один обязательный аргумент: ключ, который будет использоваться в строке запроса. Он возвращает массив со значением, присутствующим в строке запроса в виде строки (или null, если ничего не найдено), и функцию обновления состояния.
Вот простой пример того, как использовать хук useQueryState:
import { useQueryState } from 'nuqs'; function SearchComponent() { const [search, setSearch] = useQueryState('search'); return ( <input value={search ?? ''} onChange={(e) => setSearch(e.target.value)} /> ); }
Этот простой пример демонстрирует, как создать поисковый запрос, который автоматически обновляет параметр поиска URL-адреса с помощью nuqs.
Давайте посмотрим, как мы можем использовать хук useQueryState в нашем приложении для составления списка продуктов. Мы создадим пользовательский хук, в котором будем управлять всей нашей логикой и повторно использовать ее в нашей кодовой базе. В папке lib создайте файл с именем hooks/useProductParams.ts и добавьте следующий код. Мы рассмотрим детали позже:
import { useQueryState } from 'nuqs'; export function useProductParams() { const [search, setSearch] = useQueryState('search', { defaultValue: '', parse: (value) => value || '', history: 'push', }); return { search, setSearch, }; }
Здесь мы импортировали useQueryState из nuqs и создали многоразовый пользовательский хук useProductParams. Мы использовали этот хук для определения нашего параметра URL с несколькими вариантами:
-
"поиск" - это название параметра запроса в URL-адресе (например, ?поиск=одежда).
Значение по умолчанию устанавливает пустую строку в качестве значения по умолчанию
функция синтаксического анализа обрабатывает входящие значения, возвращая пустую строку, если значение равно false
история: "push" означает, что изменения создают новые записи в истории браузера
Наконец, он возвращает как текущее значение поиска, так и функцию настройки.
Теперь замените код в вашем файле SearchBar.tsx на приведенный ниже код:
'use client' import { useProductParams } from '@/lib/hooks/useProductParams'; export default function SearchBar() { const { search, setSearch } = useProductParams(); const handleSearch = (term: string) => { setSearch(term); }; return ( <div className="relative"> <input type="text" value={search} onChange={(e) => handleSearch(e.target.value)} placeholder="Search products..." className="w-full p-2 border rounded-lg text-black" /> </div> ); }
Теперь попробуйте выполнить поиск нужного элемента. При вводе текста вы увидите, что nuqs автоматически устанавливает и обновляет параметр поиска:
Однако одна из проблем с нашим кодом заключается в том, что nuqs автоматически не перерисовывает наши серверные компоненты. Это означает, что если мы выполним какую-либо фильтрацию на сервере, например, разбивку на страницы, мы не заметим никаких обновлений.
Чтобы решить эту проблему, мы установим для параметра shallow значение false в нашем перехватчике useQueryState:
// lib/hooks/useProductParams.ts const [search, setSearch] = useQueryState('search', { // other options shallow: false });
Теперь, если мы введем текст в строку поиска, мы увидим, что наши товары фильтруются при каждом нажатии клавиши.
Управление несколькими связанными ключами запроса с помощью useQueryStates
Существуют сценарии, в которых вам может потребоваться управлять несколькими связанными параметрами запроса в вашем URL-адресе, особенно когда эти параметры влияют друг на друга или когда требуется обновить несколько из них одновременно.
Например, пользователь может выполнять фильтрацию по нескольким критериям, таким как категория, диапазон цен и т.д., и сортировать по наилучшему рейтингу или значению цены, сохраняя разбивку по страницам, причем каждый фильтр представлен в URL в виде параметра запроса.
nuqs предоставляет функцию useQueryStates, которая позволяет справиться с этим путем синхронизации параметров фильтра, сортировки и разбивки на страницы с параметрами URL-запроса. Это гарантирует, что изменения в одном фильтре не вызовут ненужных повторных отображений или несоответствий, а также поддерживает пакетные обновления для повышения производительности. Давайте посмотрим, как это использовать в нашем приложении.
В нашем перехватчике useProductParams обновите код следующим образом:
// lib/hooks/useProductParams.ts import { useQueryState, useQueryStates } from 'nuqs'; export function useProductParams() { // other code const [{ category, sort, page }, setParams] = useQueryStates({ category: { defaultValue: '', parse: (value) => value || '', }, sort: { defaultValue: '', parse: (value) => value || '', }, page: { defaultValue: '1', parse: (value) => value || '1', }, }, { history: 'push', shallow: false }); const setCategory = (newCategory: string) => { setParams({ category: newCategory, page: '1' }); }; const setSort = (newSort: string) => { setParams({ sort: newSort, page: '1' }); }; const setPage = (newPage: string) => { setParams({ page: newPage }); }; return { // other variables category, setCategory, sort, setSort, page, setPage, }; }
Этот механизм перехвата аналогичен механизму перехвата useQueryState, но он принимает объект в качестве аргумента, где ключами являются ключи строки запроса, а значениями - значения по умолчанию для соответствующих переменных состояния запроса. Функции setCategory, setSort и setPage обновляют свои соответствующие параметры и, где это применимо, сбрасывают разбивку на страницы.
Теперь обновите следующие компоненты, чтобы они использовали определенные состояния.
Панель фильтров.tsx:
// components/FilterBar.tsx 'use client' import { useProductParams } from '@/lib/hooks/useProductParams'; export default function FilterBar() { const { category, setCategory, sort, setSort } = useProductParams(); // rest of the code const handleCategoryChange = (value: string) => { setCategory(value); }; const handleSortChange = (value: string) => { setSort(value); }; return ( <div className="flex gap-4 mb-4"> <select value={category} onChange={(e) => handleCategoryChange(e.target.value)} // update to use handleCategoryChange className="p-2 border rounded-lg bg-blue-500" > // rest of the code </select> <select value={sort} onChange={(e) => handleSortChange(e.target.value)} // update to use handleSortChange className="p-2 border rounded-lg bg-blue-500" > // rest of the code </select> </div> ); }
Разбивка на страницы.tsx:
// components/FilterBar.tsx 'use client' import { useProductParams } from '@/lib/hooks/useProductParams'; interface PaginationProps { totalPages: number; } export default function Pagination({ totalPages }: PaginationProps) { const { page, setPage } = useProductParams(); const currentPage = Number(page); const handlePageChange = (newPage: number) => { setPage(newPage.toString()); }; return ( // rest of the code ); }
Теперь вы можете протестировать его. Вот демонстрация:
Понимание параметров типобезопасного поиска: Синтаксические анализаторы
Параметры поиска по умолчанию представляют собой строки, но для управления более сложными типами (например, числами, логическими значениями, датами) в URL-адресах требуются типобезопасные синтаксические анализаторы.
nuqs предоставляет встроенные средства синтаксического анализа, такие как parseAsInteger, parseAsBoolean и parseAsIsoDateTime, гарантирующие проверку параметров запроса и их ввод. Например, parseAsInteger преобразует строку в целое число, в то время как parseAsBoolean интерпретирует значение true или false.
Эти анализаторы помогают управлять параметрами поиска и вводить их в действие, повышая безопасность и надежность ваших приложений. Давайте внедрим встроенный синтаксический анализатор в наше приложение со значением по умолчанию, чтобы избежать прямой проверки null в JSX, а также установим нашу предыдущую конфигурацию и сохраним чистоту нашей кодовой базы.
Возможно, вы заметили, что наши опции в hooks включают следующее:
const [search, setSearch] = useQueryState('search', { defaultValue: '', parse: (value) => value || '', history: 'push', });
Хотя этот подход работает, у него есть некоторые ограничения, такие как необходимость вручную обрабатывать нулевые или неопределенные значения, использование пустой строки по умолчанию в качестве запасного варианта и дополнительная детализация. Но благодаря встроенному синтаксическому анализатору NUQS вы можете пользоваться такими преимуществами, как безопасность типов, обработка массивов и объектов JSON.
Обновите наш пользовательский код перехвата, чтобы включить необходимые синтаксические анализаторы из nuqs:
// lib/hooks/useProductParams.ts import { useQueryState, useQueryStates, parseAsString, parseAsInteger } from 'nuqs'; export function useProductParams() { const [search, setSearch] = useQueryState('search', parseAsString.withDefault('').withOptions({ shallow: false, history: 'push' }) ); const [{ category, sort, page }, setParams] = useQueryStates({ category: parseAsString.withDefault(""), sort: parseAsString.withDefault(""), page: parseAsInteger.withDefault(1), }, { history: 'push', shallow: false }); // rest of the code }
Вам также может потребоваться обновить компонент Pagination.tsx:
const currentPage = page; // remove the type cast Number was removed setPage(newPage); // toString() was removed
Обработка состояний загрузки с переходами
Вы можете объединить useQueryState с функцией startTransition из React'а useTransition, чтобы упростить работу с пользователем, отображая состояния загрузки при повторном отображении компонентов сервером. Давайте посмотрим на это в действии:
// lib/hooks/useProductParams.ts import { useTransition } from 'react'; export function useProductParams() { const [isPending, startTransition] = useTransition(); const [search, setSearch] = useQueryState('search', parseAsString.withDefault('').withOptions({ // rest of the code startTransition }) ); const [{ category, sort, page }, setParams] = useQueryStates({ // rest of the code }, { // rest of the code startTransition }); return { // rest of the code isPending }; }
Теперь мы можем импортировать хук useProductParam и использовать его в наших компонентах:
'use client' import { useProductParams } from '@/lib/hooks/useProductParams'; import LoadingSpinner from './LoadingSpinner'; export default function SearchBar() { const { search, setSearch, isPending } = useProductParams(); // rest of the code return ( <div className="relative"> // rest of the code {isPending && ( <div className="absolute right-2 top-2"> <LoadingSpinner /> </div> )} </div> ); }
Использование nuqs в серверных компонентах на стороне сервера
nuqs также управляет параметрами поиска, безопасными для ввода, на стороне сервера, что особенно полезно для глубоко вложенных серверных компонентов.
nuqs предлагает служебную функцию под названием createSearchParamsCache, которая позволяет вам определять синтаксические анализаторы для определенных параметров поиска (например, строк, целых чисел) и безопасно получать к ним доступ в серверных компонентах. Обработанные значения сохраняются в течение всего текущего цикла рендеринга и могут быть переданы клиентским компонентам для обеспечения безопасности ввода во всем приложении. Давайте посмотрим, как использовать nuqs для правильной реализации этого.
Вот как мы ранее реализовали параметры поиска в серверном компоненте без надлежащей обработки на стороне сервера:
import { Product, SearchParams } from './types/types'; export default async function ProductsPage({ searchParams, }: { searchParams: SearchParams; }) { const { products, totalPages } = await fetchProducts(searchParams); return ( // rest of the code ); }
Этот подход имеет такие ограничения, как несогласованные значения по умолчанию, проблемы с безопасностью ввода и неопределенные параметры при первоначальном отображении, поскольку объект searchParams не был должным образом проверен. Для решения этой проблемы мы будем использовать функцию createSearchParamsCache. Это приведет к принудительному использованию значений по умолчанию на стороне сервера, даже если они отсутствуют в текущем URL-адресе.
Создайте файл searchParamsCache для настройки параметров поиска в папке lib:
// lib/searchParamsCache.ts import { createSearchParamsCache, parseAsString, parseAsInteger } from 'nuqs/server'; export const searchParamsCache = createSearchParamsCache({ search: parseAsString.withDefault(''), category: parseAsString.withDefault(''), sort: parseAsString.withDefault(''), page: parseAsInteger.withDefault(1) })
Затем используйте его в своем серверном компоненте:
// app/page.tsx import { searchParamsCache } from '@/lib/searchParamsCache'; import { SearchParams } from 'nuqs/server'; // rest of the import export default async function ProductsPage({ searchParams, }: { searchParams: Promise<SearchParams>; }) { const params = searchParamsCache.parse(await searchParams); // Fetch products with typed params const { products, totalPages } = await fetchProducts({ search: params.search, category: params.category, sort: params.sort, page: params.page }); return ( // rest of the code ); }
Интеграция с Zod
Чтобы добавить типобезопасную проверку схемы к параметрам вашего запроса с помощью Zod, нам нужно будет изменить параметр useProductParams. Здесь мы продемонстрируем, как создать пользовательский синтаксический анализатор и использовать Zod для проверки параметров нашего запроса. Мы продемонстрируем это только для параметра сортировки, но вы можете добавить проверку для других параметров.
Сначала установите Zod с помощью npm install zod, затем определите схемы Zod и проверьте параметры сортировки с помощью перечислений Zod и создайте пользовательский синтаксический анализатор, который использует Zod для проверки. В свой файл useProductParams добавьте приведенный ниже код:
import { useQueryState, useQueryStates, parseAsString, parseAsInteger, createParser } from 'nuqs'; const SortSchema = z.enum(['', 'price-asc', 'price-desc', 'rating']); type SortOption = z.infer<typeof SortSchema>; const zodSortParser = createParser({ parse: (value: string | null): SortOption => { const result = SortSchema.safeParse(value ?? ''); return result.success ? result.data : ''; }, serialize: (value: SortOption) => value, });
Теперь, внутри перехватчика useProductParams, измените параметр сортировки, чтобы использовать синтаксический анализатор, проверенный Zod:
sort: zodSortParser.withDefault('' as SortOption),
Теперь обновим код в нашем компоненте FilterBar.tsx:
// rest of the code const sortOptions = [ { value: '', label: 'Default' }, { value: 'price-asc', label: 'Price: Low to High' }, { value: 'price-desc', label: 'Price: High to Low' }, { value: 'rating', label: 'Best Rating' } ] as const; type SortOption = typeof sortOptions\[number\]['value']; export default function FilterBar() { // rest of the codd return ( <div className="flex gap-4 mb-4"> // rest of the code <select value={sort} onChange={(e) => handleSortChange(e.target.value as SortOption)} // update this className="p-2 border rounded-lg bg-blue-500" > // rest of the code </select> </div> ); }
Наша сортировочная фильтрация теперь обеспечивает безопасность типов Zod.
Когда использовать nuqs и Других государственных управленческих решений
Несмотря на то, что nuqs отлично справляется с управлением состояниями на основе URL, важно учитывать, когда использовать его по сравнению с другими решениями для управления состояниями. nuqs полезен, когда:
-
Вам необходимо поддерживать URL-адреса с отслеживанием состояния для публикации или добавления в закладки
SEO - это приоритет
Вы хотите, чтобы поисковые системы понимали состояние вашей страницы
Создавая платформу, подобную магазину электронной коммерции, вы хотите, чтобы такие функции, как поиск, фильтрация или разбивка на страницы, естественным образом соответствовали параметрам URL
В качестве альтернативы, nuq не следует рассматривать, когда:
-
Работа со сложными вложенными состояниями, которые плохо согласуются с параметрами URL-адреса
Управление большими объемами данных, которые могут сделать URL-адреса громоздкими
Обработка конфиденциальной информации, которая не должна отображаться в URL-адресе
Вывод
В этой статье мы рассмотрели, как nuqs значительно упрощает управление параметрами поиска в приложениях Next.js. Благодаря удобному набору текста, пользовательским сериализаторам и интеграции с Zod, nuqs выводит управление состоянием на основе URL на новый уровень, помогая создавать приложения, которые легко доступны для совместного использования и удобны для SEO.
Мы рассмотрели настройку nuqs в проекте Next.js, синхронизацию фильтров, сортировки и разбивки на страницы с параметрами URL и использование встроенных синтаксических анализаторов для обеспечения согласованности типов. Сокращая количество шаблонных кодов и повышая согласованность, nuqs позволяет разработчикам сосредоточиться на обеспечении бесперебойного взаимодействия с пользователями.
Независимо от того, создаете ли вы простую функцию поиска или полноценный сайт электронной коммерции, nuqs предоставляет упрощенный, многократно используемый способ обработки параметров запроса и поддержания чистоты и упорядоченности вашего кода.