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
— Находится ли приложение в режиме предпросмотра (preview mode).
Использование поля
asPath
может привести к несоответствию между клиентом и сервером, если страница рендерится с использованием рендеринга на стороне сервера или автоматической статической оптимизации. Избегайте использования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. Вот список поддерживаемых событий:
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 routing`
)
}
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} />
}
Потенциальные ошибки 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)