useRouter
Если вам нужно получить доступ к объекту router
внутри любого функционального компонента вашего приложения, вы можете использовать хук useRouter
. Рассмотрим следующий пример:
import { useRouter } from 'next/router'
function ActiveLink({ children, href }) {
const router = useRouter()
const style = {
marginRight: 10,
color: router.asPath === href ? 'red' : 'black',
}
const handleClick = (e) => {
e.preventDefault()
router.push(href)
}
return (
<a href={href} onClick={handleClick} style={style}>
{children}
</a>
)
}
export default ActiveLink
useRouter
— это React Hook, что означает, что его нельзя использовать с классами. Вы можете либо использовать withRouter, либо обернуть ваш класс в функциональный компонент.
Объект router
Ниже приведено определение объекта router
, который возвращают как useRouter
, так и withRouter
:
pathname
:String
— Путь текущего маршрута, соответствующий файлу в/pages
. При этомbasePath
,locale
и завершающий слеш (trailingSlash: true
) не включаются.query
:Object
— Строка запроса, преобразованная в объект, включая параметры динамических маршрутов. Будет пустым объектом во время предварительного рендеринга, если страница не использует рендеринг на стороне сервера (SSR). По умолчанию{}
.asPath
:String
— Путь, отображаемый в браузере, включая параметры поиска и учитывающий настройкуtrailingSlash
.basePath
иlocale
не включаются.isFallback
:boolean
— Находится ли текущая страница в режиме fallback.basePath
:String
— Активный basePath (если включен).locale
:String
— Активная локаль (если включена).locales
:String[]
— Все поддерживаемые локали (если включены).defaultLocale
:String
— Текущая локаль по умолчанию (если включена).domainLocales
:Array<{domain, defaultLocale, locales}>
— Настроенные доменные локали.isReady
:boolean
— Обновлены ли поля роутера на стороне клиента и готовы ли они к использованию. Следует использовать только внутри методовuseEffect
и не для условного рендеринга на сервере. См. соответствующую документацию для автоматически статически оптимизированных страниц.isPreview
:boolean
— Находится ли приложение в режиме предпросмотра.
Использование поля
asPath
может привести к несоответствию между клиентом и сервером, если страница рендерится с использованием SSR или автоматической статической оптимизации. Избегайте использованияasPath
, пока полеisReady
не станетtrue
.
Следующие методы доступны внутри объекта router
:
router.push
Обрабатывает переходы на стороне клиента. Этот метод полезен, когда next/link
недостаточно.
router.push(url, as, options)
url
:UrlObject | String
— URL для перехода (см. документацию модуля URL Node.JS для свойствUrlObject
).as
:UrlObject | String
— Необязательный декоратор для пути, который будет отображаться в адресной строке браузера. До Next.js 9.5.3 использовался для динамических маршрутов.options
— Необязательный объект со следующими настройками:scroll
— Необязательный boolean, управляет прокруткой к верху страницы после навигации. По умолчаниюtrue
.shallow
: Обновляет путь текущей страницы без повторного выполненияgetStaticProps
,getServerSideProps
илиgetInitialProps
. По умолчаниюfalse
.locale
— Необязательная строка, указывает локаль новой страницы.
Для внешних URL не нужно использовать
router.push
. Лучше подойдет window.location.
Переход на pages/about.js
(предопределенный маршрут):
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/about')}>
Нажми меня
</button>
)
}
Переход на pages/post/[pid].js
(динамический маршрут):
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/post/abc')}>
Нажми меня
</button>
)
}
Перенаправление пользователя на pages/login.js
(полезно для страниц с аутентификацией):
import { useEffect } from 'react'
import { useRouter } from 'next/router'
// Здесь вы бы получали и возвращали пользователя
const useUser = () => ({ user: null, loading: false })
export default function Page() {
const { user, loading } = useUser()
const router = useRouter()
useEffect(() => {
if (!(user || loading)) {
router.push('/login')
}
}, [user, loading])
return <p>Перенаправление...</p>
}
Сброс состояния после навигации
При переходе на ту же страницу в Next.js состояние страницы не сбрасывается по умолчанию, так как React не размонтирует компонент, если родительский компонент не изменился.
import Link from 'next/link'
import { useState } from 'react'
import { useRouter } from 'next/router'
export default function Page(props) {
const router = useRouter()
const [count, setCount] = useState(0)
return (
<div>
<h1>Страница: {router.query.slug}</h1>
<p>Счетчик: {count}</p>
<button onClick={() => setCount(count + 1)}>Увеличить счетчик</button>
<Link href="/one">one</Link> <Link href="/two">two</Link>
</div>
)
}
В примере выше переход между /one
и /two
не сбросит счетчик. Состояние useState
сохраняется между рендерами, потому что корневой React-компонент Page
остается тем же.
Если вам нужно изменить это поведение, есть несколько вариантов:
-
Вручную обновлять состояние с помощью
useEffect
. В примере выше это может выглядеть так:useEffect(() => { setCount(0) }, [router.query.slug])
-
Использовать React
key
, чтобы указать React перемонтировать компонент. Для всех страниц можно использовать кастомное приложение:pages/_app.js import { useRouter } from 'next/router' export default function MyApp({ Component, pageProps }) { const router = useRouter() return <Component key={router.asPath} {...pageProps} /> }
С объектом URL
Вы можете использовать объект URL так же, как и для next/link
. Работает для обоих параметров url
и as
:
import { useRouter } from 'next/router'
export default function ReadMore({ post }) {
const router = useRouter()
return (
<button
type="button"
onClick={() => {
router.push({
pathname: '/post/[pid]',
query: { pid: post.id },
})
}}
>
Нажмите, чтобы прочитать больше
</button>
)
}
router.replace
Аналогично пропсу replace
в next/link
, router.replace
предотвращает добавление новой записи URL в стек истории.
router.replace(url, as, options)
- API для
router.replace
полностью совпадает с API дляrouter.push
.
Пример:
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.replace('/home')}>
Нажми меня
</button>
)
}
router.prefetch
Предварительно загружает страницы для более быстрых переходов на стороне клиента. Этот метод полезен только для навигации без next/link
, так как next/link
автоматически предзагружает страницы.
Эта функция работает только в production. Next.js не предзагружает страницы в режиме разработки.
router.prefetch(url, as, options)
url
— URL для предзагрузки, включая явные маршруты (например,/dashboard
) и динамические маршруты (например,/product/[id]
).as
— Необязательный декоратор дляurl
. До Next.js 9.5.3 использовался для предзагрузки динамических маршрутов.options
— Необязательный объект со следующими допустимыми полями:locale
— Позволяет указать локаль, отличную от активной. Еслиfalse
,url
должен включать локаль, так как активная локаль не будет использоваться.
Допустим, у вас есть страница входа, и после входа вы перенаправляете пользователя на панель управления. В этом случае можно предзагрузить панель управления для более быстрого перехода, как в следующем примере:
import { useCallback, useEffect } from 'react'
import { useRouter } from 'next/router'
export default function Login() {
const router = useRouter()
const handleSubmit = useCallback((e) => {
e.preventDefault()
fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
/* Данные формы */
}),
}).then((res) => {
// Быстрый переход на предзагруженную страницу панели управления
if (res.ok) router.push('/dashboard')
})
}, [])
useEffect(() => {
// Предзагрузка страницы панели управления
router.prefetch('/dashboard')
}, [router])
return (
<form onSubmit={handleSubmit}>
{/* Поля формы */}
<button type="submit">Войти</button>
</form>
)
}
router.beforePopState
В некоторых случаях (например, при использовании кастомного сервера) может потребоваться прослушивать событие popstate и выполнять действия до того, как роутер обработает его.
router.beforePopState(cb)
cb
— Функция, которая выполняется при входящих событияхpopstate
. Функция получает состояние события в виде объекта со следующими свойствами:url
:String
— Маршрут для нового состояния. Обычно это имя страницы (page
).as
:String
— URL, который будет отображаться в браузере.options
:Object
— Дополнительные опции, отправленные router.push.
Если cb
возвращает false
, Next.js роутер не будет обрабатывать popstate
, и вам нужно будет обработать его самостоятельно. См. Отключение файловой маршрутизации.
Можно использовать beforePopState
для изменения запроса или принудительного SSR-обновления, как в следующем примере:
import { useEffect } from 'react'
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
useEffect(() => {
router.beforePopState(({ url, as, options }) => {
// Я хочу разрешить только эти два маршрута!
if (as !== '/' && as !== '/other') {
// SSR должен рендерить некорректные маршруты как 404.
window.location.href = as
return false
}
return true
})
}, [router])
return <p>Добро пожаловать на страницу</p>
}
router.back
Переход назад в истории. Эквивалентно нажатию кнопки "Назад" в браузере. Выполняет window.history.back()
.
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.back()}>
Нажмите, чтобы вернуться назад
</button>
)
}
router.reload
Перезагружает текущий URL. Эквивалентно нажатию кнопки "Обновить" в браузере. Выполняет window.location.reload()
.
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.reload()}>
Нажмите, чтобы перезагрузить
</button>
)
}
router.events
Вы можете подписаться на различные события, происходящие внутри Next.js Router. Вот список поддерживаемых событий:
routeChangeStart(url, { shallow })
— Срабатывает при начале изменения маршрута.routeChangeComplete(url, { shallow })
— Срабатывает при полном изменении маршрута.routeChangeError(err, url, { shallow })
— Срабатывает при ошибке изменения маршрута или отмене загрузки маршрута.err.cancelled
— Указывает, была ли навигация отменена.
beforeHistoryChange(url, { shallow })
— Срабатывает перед изменением истории браузера.hashChangeStart(url, { shallow })
— Срабатывает при изменении хэша, но не страницы.hashChangeComplete(url, { shallow })
— Срабатывает после изменения хэша, но не страницы.
Важно: Здесь
url
— это URL, отображаемый в браузере, включаяbasePath
.
Например, чтобы подписаться на событие routeChangeStart
, откройте или создайте pages/_app.js
и подпишитесь на событие следующим образом:
import { useEffect } from 'react'
import { useRouter } from 'next/router'
export default function MyApp({ Component, pageProps }) {
const router = useRouter()
useEffect(() => {
const handleRouteChange = (url, { shallow }) => {
console.log(
`Приложение переходит на ${url} ${
shallow ? 'с' : 'без'
} shallow-маршрутизации`
)
}
router.events.on('routeChangeStart', handleRouteChange)
// При размонтировании компонента отписываемся от события методом 'off':
return () => {
router.events.off('routeChangeStart', handleRouteChange)
}
}, [router])
return <Component {...pageProps} />
}
В этом примере мы используем Кастомное приложение (
pages/_app.js
) для подписки на событие, потому что оно не размонтируется при навигации по страницам. Однако вы можете подписаться на события роутера в любом компоненте вашего приложения.
События роутера следует регистрировать при монтировании компонента (useEffect или componentDidMount / componentWillUnmount) или императивно при возникновении события.
Если загрузка маршрута отменена (например, при быстром нажатии на две ссылки подряд), сработает routeChangeError
. Переданный err
будет содержать свойство cancelled
со значением true
, как в следующем примере:
import { useEffect } from 'react'
import { useRouter } from 'next/router'
export default function MyApp({ Component, pageProps }) {
const router = useRouter()
useEffect(() => {
const handleRouteChangeError = (err, url) => {
if (err.cancelled) {
console.log(`Переход на ${url} был отменен!`)
}
}
router.events.on('routeChangeError', handleRouteChangeError)
// При размонтировании компонента отписываемся от события методом 'off':
return () => {
router.events.off('routeChangeError', handleRouteChangeError)
}
}, [router])
return <Component {...pageProps} />
}
Экспорт next/compat/router
Это тот же самый хук useRouter
, но его можно использовать как в директории app
, так и в pages
.
Он отличается от next/router
тем, что не выбрасывает ошибку, когда роутер страниц не подключен, а вместо этого имеет тип возвращаемого значения NextRouter | null
. Это позволяет разработчикам адаптировать компоненты для работы как в app
, так и в pages
во время перехода на роутер app
.
Компонент, который раньше выглядел так:
import { useRouter } from 'next/router'
const MyComponent = () => {
const { isReady, query } = useRouter()
// ...
}
Выдаст ошибку при переходе на next/compat/router
, так как null
нельзя деструктуризировать. Вместо этого разработчики могут воспользоваться новыми хуками:
import { useEffect } from 'react'
import { useRouter } from 'next/compat/router'
import { useSearchParams } from 'next/navigation'
const MyComponent = () => {
const router = useRouter() // может быть null или экземпляром NextRouter
const searchParams = useSearchParams()
useEffect(() => {
if (router && !router.isReady) {
return
}
// В `app/` searchParams будут доступны сразу со значениями,
// в `pages/` они станут доступны после готовности роутера.
const search = searchParams.get('search')
// ...
}, [router, searchParams])
// ...
}
Теперь этот компонент будет работать как в директории pages
, так и в app
. Когда компонент больше не используется в pages
, можно удалить ссылки на совместимый роутер:
import { useSearchParams } from 'next/navigation'
const MyComponent = () => {
const searchParams = useSearchParams()
// Так как компонент используется только в `app/`, совместимый роутер можно удалить.
const search = searchParams.get('search')
// ...
}
Использование useRouter
вне контекста Next.js в pages
Еще один частный случай — рендеринг компонентов вне контекста приложения Next.js, например, внутри getServerSideProps
в директории pages
. В этом случае можно использовать совместимый роутер, чтобы избежать ошибок:
import { renderToString } from 'react-dom/server'
import { useRouter } from 'next/compat/router'
const MyComponent = () => {
const router = useRouter() // может быть null или экземпляром NextRouter
// ...
}
export async function getServerSideProps() {
const renderedComponent = renderToString(<MyComponent />)
return {
props: {
renderedComponent,
},
}
}
Возможные ошибки ESLint
Некоторые методы объекта router
возвращают Promise. Если у вас включено правило ESLint no-floating-promises, рассмотрите возможность отключить его глобально или для конкретной строки.
Если ваше приложение требует этого правила, вы можете либо использовать void
для Promise, либо использовать async
-функцию, await
для Promise и затем void
для вызова функции. Это не применимо, когда метод вызывается внутри обработчика onClick
.
Затронутые методы:
router.push
router.replace
router.prefetch
Возможные решения
import { useEffect } from 'react'
import { useRouter } from 'next/router'
// Здесь вы бы получили и вернули пользователя
const useUser = () => ({ user: null, loading: false })
export default function Page() {
const { user, loading } = useUser()
const router = useRouter()
useEffect(() => {
// отключить линтинг для следующей строки — это самое чистое решение
// eslint-disable-next-line no-floating-promises
router.push('/login')
// использовать void для Promise, возвращаемого router.push
if (!(user || loading)) {
void router.push('/login')
}
// или использовать async-функцию, await для Promise и затем void для вызова
async function handleRouteChange() {
if (!(user || loading)) {
await router.push('/login')
}
}
void handleRouteChange()
}, [user, loading])
return <p>Перенаправление...</p>
}
withRouter
Если useRouter
вам не подходит, withRouter
также может добавить тот же router
объект к любому компоненту.
Использование
import { withRouter } from 'next/router'
function Page({ router }) {
return <p>{router.pathname}</p>
}
export default withRouter(Page)
TypeScript
Для использования классовых компонентов с withRouter
компонент должен принимать проп router
:
import React from 'react'
import { withRouter, NextRouter } from 'next/router'
interface WithRouterProps {
router: NextRouter
}
interface MyComponentProps extends WithRouterProps {}
class MyComponent extends React.Component<MyComponentProps> {
render() {
return <p>{this.props.router.pathname}</p>
}
}
export default withRouter(MyComponent)