Skip to main content

useRouter

如果你想访问应用中任何功能组件内的 router 对象,你可以使用 useRouter 钩子,请看以下示例:

¥If you want to access the router object inside any function component in your app, you can use the useRouter hook, take a look at the following example:

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} >
{children}
</a>
)
}

export default ActiveLink

useRouterReact 钩子,这意味着它不能与类一起使用。你可以使用 withRouter 或将你的类封装在函数组件中。

¥useRouter is a React Hook, meaning it cannot be used with classes. You can either use withRouter or wrap your class in a function component.

router 对象

¥router object

以下是 useRouterwithRouter 返回的 router 对象的定义:

¥The following is the definition of the router object returned by both useRouter and withRouter:

  • pathnameString - /pages 之后的当前路由文件的路径。因此,不包括 basePathlocale 和尾部斜杠 (trailingSlash: true)。

    ¥pathname: String - The path for current route file that comes after /pages. Therefore, basePath, locale and trailing slash (trailingSlash: true) are not included.

  • queryObject - 查询字符串解析为对象,包含 动态路由 个参数。如果页面不使用 服务端渲染,则在预渲染期间它将是一个空对象。默认为 {}

    ¥query: Object - The query string parsed to an object, including dynamic route parameters. It will be an empty object during prerendering if the page doesn't use Server-side Rendering. Defaults to {}

  • asPathString - 浏览器中显示的路径包括搜索参数并遵循 trailingSlash 配置。不包括 basePathlocale

    ¥asPath: String - The path as shown in the browser including the search params and respecting the trailingSlash configuration. basePath and locale are not included.

  • isFallbackboolean - 当前页面是否在 后备模式

    ¥isFallback: boolean - Whether the current page is in fallback mode.

  • basePathString - 活动 basePath(如果启用)。

    ¥basePath: String - The active basePath (if enabled).

  • localeString - 活动区域设置(如果启用)。

    ¥locale: String - The active locale (if enabled).

  • localesString[] - 所有受支持的区域设置(如果启用)。

    ¥locales: String[] - All supported locales (if enabled).

  • defaultLocaleString - 当前的默认区域设置(如果启用)。

    ¥defaultLocale: String - The current default locale (if enabled).

  • domainLocalesArray<{domain, defaultLocale, locales}> - 任何配置的域区域设置。

    ¥domainLocales: Array<{domain, defaultLocale, locales}> - Any configured domain locales.

  • isReadyboolean - 路由字段是否已在客户端更新并可供使用。只能在 useEffect 方法内部使用,不能用于服务器上的条件渲染。请参阅相关文档了解 自动静态优化页面 的用例

    ¥isReady: boolean - Whether the router fields are updated client-side and ready for use. Should only be used inside of useEffect methods and not for conditionally rendering on the server. See related docs for use case with automatically statically optimized pages

  • isPreviewboolean - 应用当前是否处于 预览模式

    ¥isPreview: boolean - Whether the application is currently in preview mode.

如果页面使用服务器端渲染或 自动静态优化 渲染,则使用 asPath 字段可能会导致客户端和服务器之间不匹配。在 isReady 字段为 true 之前,避免使用 asPath

¥Using the asPath field may lead to a mismatch between client and server if the page is rendered using server-side rendering or automatic static optimization. Avoid using asPath until the isReady field is true.

router 内部包含以下方法:

¥The following methods are included inside router:

router.push

处理客户端转换,此方法对于 next/link 不够的情况很有用。

¥Handles client-side transitions, this method is useful for cases where next/link is not enough.

router.push(url, as, options)
  • urlUrlObject | String - 要导航到的 URL(请参阅 Node.JS URL 模块文档 了解 UrlObject 属性)。

    ¥url: UrlObject | String - The URL to navigate to (see Node.JS URL module documentation for UrlObject properties).

  • asUrlObject | String - 将显示在浏览器 URL 栏中的路径的可选装饰器。在 Next.js 9.5.3 之前,这用于动态路由。

    ¥as: UrlObject | String - Optional decorator for the path that will be shown in the browser URL bar. Before Next.js 9.5.3 this was used for dynamic routes.

  • options - 具有以下配置选项的可选对象:

    ¥options - Optional object with the following configuration options:

    • scroll - 可选布尔值,控制导航后滚动到页面顶部。默认为 true

      ¥scroll - Optional boolean, controls scrolling to the top of the page after navigation. Defaults to true

    • shallow:更新当前页面的路径,无需重新运行 getStaticPropsgetServerSidePropsgetInitialProps。默认为 false

      ¥shallow: Update the path of the current page without rerunning getStaticProps, getServerSideProps or getInitialProps. Defaults to false

    • locale - 可选字符串,指示新页面的区域设置

      ¥locale - Optional string, indicates locale of the new page

你不需要将 router.push 用于外部 URL。window.location 更适合这些情况。

¥You don't need to use router.push for external URLs. window.location is better suited for those cases.

导航到 pages/about.js,这是预定义的路由:

¥Navigating to pages/about.js, which is a predefined route:

import { useRouter } from 'next/router'

export default function Page() {
const router = useRouter()

return (
<button type="button" onClick={() => router.push('/about')}>
Click me
</button>
)
}

导航 pages/post/[pid].js,这是一条动态路由:

¥Navigating pages/post/[pid].js, which is a dynamic route:

import { useRouter } from 'next/router'

export default function Page() {
const router = useRouter()

return (
<button type="button" onClick={() => router.push('/post/abc')}>
Click me
</button>
)
}

将用户重定向到 pages/login.js,对于 authentication 后面的页面很有用:

¥Redirecting the user to pages/login.js, useful for pages behind authentication:

import { useEffect } from 'react'
import { useRouter } from 'next/router'

// Here you would fetch and return the user
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>Redirecting...</p>
}

导航后重置状态

¥Resetting state after navigation

当导航到 Next.js 中的同一页面时,默认情况下不会重置页面的状态,因为除非父组件发生更改,否则 React 不会卸载。

¥When navigating to the same page in Next.js, the page's state will not be reset by default as React does not unmount unless the parent component has changed.

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>Page: {router.query.slug}</h1>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increase count</button>
<Link href="/one">one</Link> <Link href="/two">two</Link>
</div>
)
}

在上面的示例中,在 /one/two 之间导航不会重置计数。 useState 在渲染之间保持不变,因为顶层 React 组件 Page 是相同的。

¥In the above example, navigating between /one and /two will not reset the count . The useState is maintained between renders because the top-level React component, Page, is the same.

如果你不希望出现这种行为,你有以下几种选择:

¥If you do not want this behavior, you have a couple of options:

  • 使用 useEffect 手动确保每个状态都已更新。在上面的示例中,可能如下所示:

    ¥Manually ensure each state is updated using useEffect. In the above example, that could look like:

    useEffect(() => {
    setCount(0)
    }, [router.query.slug])
  • 使用 React key告诉 React 重新挂载组件。要对所有页面执行此操作,你可以使用自定义应用:

    ¥Use a React key to tell React to remount the component. To do this for all pages, you can use a custom app:

    import { useRouter } from 'next/router'

    export default function MyApp({ Component, pageProps }) {
    const router = useRouter()
    return <Component key={router.asPath} {...pageProps} />
    }

带有 URL 对象

¥With URL object

你可以按照与 next/link 相同的方式使用 URL 对象。适用于 urlas 参数:

¥You can use a URL object in the same way you can use it for next/link. Works for both the url and as parameters:

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 },
})
}}
>
Click here to read more
</button>
)
}

router.replace

next/link 中的 replace 属性类似,router.replace 将阻止向 history 堆栈中添加新的 URL 条目。

¥Similar to the replace prop in next/link, router.replace will prevent adding a new URL entry into the history stack.

router.replace(url, as, options)
  • router.replace 的 API 与 router.push 的 API 完全相同。

    ¥The API for router.replace is exactly the same as the API for router.push.

看一下下面的例子:

¥Take a look at the following example:

import { useRouter } from 'next/router'

export default function Page() {
const router = useRouter()

return (
<button type="button" onClick={() => router.replace('/home')}>
Click me
</button>
)
}

router.prefetch

预取页面以实现更快的客户端转换。此方法仅适用于没有 next/link 的导航,因为 next/link 会自动处理预取页面。

¥Prefetch pages for faster client-side transitions. This method is only useful for navigations without next/link, as next/link takes care of prefetching pages automatically.

这是仅限生产的功能。Next.js 在开发中不会预取页面。

¥This is a production only feature. Next.js doesn't prefetch pages in development.

router.prefetch(url, as, options)
  • url - 要预取的 URL,包括显式路由(例如 /dashboard)和动态路由(例如 /product/[id]

    ¥url - The URL to prefetch, including explicit routes (e.g. /dashboard) and dynamic routes (e.g. /product/[id])

  • as - url 的可选装饰器。在 Next.js 9.5.3 之前,这用于预取动态路由。

    ¥as - Optional decorator for url. Before Next.js 9.5.3 this was used to prefetch dynamic routes.

  • options - 具有以下允许字段的可选对象:

    ¥options - Optional object with the following allowed fields:

    • locale - 允许提供与活动区域不同的区域设置。如果 falseurl 必须包含区域设置,因为不会使用活动区域设置。

      ¥locale - allows providing a different locale from the active one. If false, url has to include the locale as the active locale won't be used.

假设你有一个登录页面,登录后,你将用户重定向到仪表板。对于这种情况,我们可以预取仪表板以实现更快的转换,如下例所示:

¥Let's say you have a login page, and after a login, you redirect the user to the dashboard. For that case, we can prefetch the dashboard to make a faster transition, like in the following example:

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({
/* Form data */
}),
}).then((res) => {
// Do a fast client-side transition to the already prefetched dashboard page
if (res.ok) router.push('/dashboard')
})
}, [])

useEffect(() => {
// Prefetch the dashboard page
router.prefetch('/dashboard')
}, [router])

return (
<form onSubmit={handleSubmit}>

<button type="submit">Login</button>
</form>
)
}

router.beforePopState

在某些情况下(例如,如果使用 自定义服务器),你可能希望监听 popstate 并在路由对其进行操作之前执行某些操作。

¥In some cases (for example, if using a Custom Server), you may wish to listen to popstate and do something before the router acts on it.

router.beforePopState(cb)
  • cb - 在传入 popstate 事件时运行的函数。该函数将事件状态作为具有以下属性的对象接收:

    ¥cb - The function to run on incoming popstate events. The function receives the state of the event as an object with the following props:

    • urlString - 新状态的路由。这通常是 page 的名字

      ¥url: String - the route for the new state. This is usually the name of a page

    • asString - 将在浏览器中显示的 url

      ¥as: String - the url that will be shown in the browser

    • optionsObject - router.push 发送的附加选项

      ¥options: Object - Additional options sent by router.push

如果 cb 返回 false,Next.js 路由将不会处理 popstate,在这种情况下你将负责处理它。参见 禁用文件系统路由

¥If cb returns false, the Next.js router will not handle popstate, and you'll be responsible for handling it in that case. See Disabling file-system routing.

你可以使用 beforePopState 来操纵请求,或强制 SSR 刷新,如下例所示:

¥You could use beforePopState to manipulate the request, or force a SSR refresh, as in the following example:

import { useEffect } from 'react'
import { useRouter } from 'next/router'

export default function Page() {
const router = useRouter()

useEffect(() => {
router.beforePopState(({ url, as, options }) => {
// I only want to allow these two routes!
if (as !== '/' && as !== '/other') {
// Have SSR render bad routes as a 404.
window.location.href = as
return false
}

return true
})
}, [router])

return <p>Welcome to the page</p>
}

router.back

回顾历史。相当于点击浏览器的后退按钮。它执行 window.history.back()

¥Navigate back in history. Equivalent to clicking the browser’s back button. It executes window.history.back().

import { useRouter } from 'next/router'

export default function Page() {
const router = useRouter()

return (
<button type="button" onClick={() => router.back()}>
Click here to go back
</button>
)
}

router.reload

重新加载当前 URL。相当于点击浏览器的刷新按钮。它执行 window.location.reload()

¥Reload the current URL. Equivalent to clicking the browser’s refresh button. It executes window.location.reload().

import { useRouter } from 'next/router'

export default function Page() {
const router = useRouter()

return (
<button type="button" onClick={() => router.reload()}>
Click here to reload
</button>
)
}

router.events

你可以监听 Next.js Router 内发生的不同事件。以下是支持的事件列表:

¥You can listen to different events happening inside the Next.js Router. Here's a list of supported events:

  • routeChangeStart(url, { shallow }) - 当路由开始改变时触发

    ¥routeChangeStart(url, { shallow }) - Fires when a route starts to change

  • routeChangeComplete(url, { shallow }) - 当路由完全改变时触发

    ¥routeChangeComplete(url, { shallow }) - Fires when a route changed completely

  • routeChangeError(err, url, { shallow }) - 当更改路由时出现错误或路由加载被取消时触发

    ¥routeChangeError(err, url, { shallow }) - Fires when there's an error when changing routes, or a route load is cancelled

    • err.cancelled - 指示导航是否已取消

      ¥err.cancelled - Indicates if the navigation was cancelled

  • beforeHistoryChange(url, { shallow }) - 在更改浏览器历史记录之前触发

    ¥beforeHistoryChange(url, { shallow }) - Fires before changing the browser's history

  • hashChangeStart(url, { shallow }) - 当哈希值发生变化但页面不变时触发

    ¥hashChangeStart(url, { shallow }) - Fires when the hash will change but not the page

  • hashChangeComplete(url, { shallow }) - 当哈希值已更改但页面未更改时触发

    ¥hashChangeComplete(url, { shallow }) - Fires when the hash has changed but not the page

很高兴知道:这里的 url 是浏览器中显示的 URL,包括 basePath

¥Good to know: Here url is the URL shown in the browser, including the basePath.

例如,要监听路由事件 routeChangeStart,请打开或创建 pages/_app.js 并订阅该事件,如下所示:

¥For example, to listen to the router event routeChangeStart, open or create pages/_app.js and subscribe to the event, like so:

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(
`App is changing to ${url} ${
shallow ? 'with' : 'without'
} shallow routing`
)
}

router.events.on('routeChangeStart', handleRouteChange)

// If the component is unmounted, unsubscribe
// from the event with the `off` method:
return () => {
router.events.off('routeChangeStart', handleRouteChange)
}
}, [router])

return <Component {...pageProps} />
}

我们在本示例中使用 自定义应用 (pages/_app.js) 来订阅事件,因为它不会在页面导航上卸载,但你可以在应用中的任何组件上订阅路由事件。

¥We use a Custom App (pages/_app.js) for this example to subscribe to the event because it's not unmounted on page navigations, but you can subscribe to router events on any component in your application.

路由事件应在组件安装(useEffectcomponentDidMount / componentWillUnmount)时注册,或者在事件发生时强制注册。

¥Router events should be registered when a component mounts (useEffect or componentDidMount / componentWillUnmount) or imperatively when an event happens.

如果路由加载被取消(例如,通过连续快速单击两个链接),routeChangeError 将触发。并且传递的 err 将包含设置为 truecancelled 属性,如下例所示:

¥If a route load is cancelled (for example, by clicking two links rapidly in succession), routeChangeError will fire. And the passed err will contain a cancelled property set to true, as in the following example:

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(`Route to ${url} was cancelled!`)
}
}

router.events.on('routeChangeError', handleRouteChangeError)

// If the component is unmounted, unsubscribe
// from the event with the `off` method:
return () => {
router.events.off('routeChangeError', handleRouteChangeError)
}
}, [router])

return <Component {...pageProps} />
}

next/compat/router 导出

¥The next/compat/router export

这是相同的 useRouter 钩子,但可以在 apppages 目录中使用。

¥This is the same useRouter hook, but can be used in both app and pages directories.

它与 next/router 的不同之处在于,当未安装页面路由时,它不会抛出错误,而是具有返回类型 NextRouter | null。这允许开发者在过渡到 app 路由时转换组件以支持在 apppages 中运行。

¥It differs from next/router in that it does not throw an error when the pages router is not mounted, and instead has a return type of NextRouter | null. This allows developers to convert components to support running in both app and pages as they transition to the app router.

以前看起来像这样的组件:

¥A component that previously looked like this:

import { useRouter } from 'next/router'
const MyComponent = () => {
const { isReady, query } = useRouter()
// ...
}

转换为 next/compat/router 时会出错,因为 null 无法解构。相反,开发者将能够利用新的钩子:

¥Will error when converted over to next/compat/router, as null can not be destructured. Instead, developers will be able to take advantage of new hooks:

import { useEffect } from 'react'
import { useRouter } from 'next/compat/router'
import { useSearchParams } from 'next/navigation'
const MyComponent = () => {
const router = useRouter() // may be null or a NextRouter instance
const searchParams = useSearchParams()
useEffect(() => {
if (router && !router.isReady) {
return
}
// In `app/`, searchParams will be ready immediately with the values, in
// `pages/` it will be available after the router is ready.
const search = searchParams.get('search')
// ...
}, [router, searchParams])
// ...
}

此组件现在可在 pagesapp 目录中工作。当组件不再在 pages 中使用时,你可以删除对兼容路由的引用:

¥This component will now work in both pages and app directories. When the component is no longer used in pages, you can remove the references to the compat router:

import { useSearchParams } from 'next/navigation'
const MyComponent = () => {
const searchParams = useSearchParams()
// As this component is only used in `app/`, the compat router can be removed.
const search = searchParams.get('search')
// ...
}

在页面中的 Next.js 上下文之外使用 useRouter

¥Using useRouter outside of Next.js context in pages

另一个特定用例是在 Next.js 应用上下文之外渲染组件时,例如在 pages 目录上的 getServerSideProps 内部。在这种情况下,可以使用兼容路由来避免错误:

¥Another specific use case is when rendering components outside of a Next.js application context, such as inside getServerSideProps on the pages directory. In this case, the compat router can be used to avoid errors:

import { renderToString } from 'react-dom/server'
import { useRouter } from 'next/compat/router'
const MyComponent = () => {
const router = useRouter() // may be null or a NextRouter instance
// ...
}
export async function getServerSideProps() {
const renderedComponent = renderToString(<MyComponent />)
return {
props: {
renderedComponent,
},
}
}

潜在的 ESLint 错误

¥Potential ESLint errors

router 对象上可访问的某些方法会返回 Promise。如果你启用了 ESLint 规则 no-floating-promises,请考虑全局禁用它或针对受影响的行禁用它。

¥Certain methods accessible on the router object return a Promise. If you have the ESLint rule, no-floating-promises enabled, consider disabling it either globally, or for the affected line.

如果你的应用需要此规则,你应该 void promise - 或使用 async 函数,await promise,然后无效函数调用。当从 onClick 处理程序内部调用该方法时,这不适用。

¥If your application needs this rule, you should either void the promise – or use an async function, await the Promise, then void the function call. This is not applicable when the method is called from inside an onClick handler.

受影响的方法有:

¥The affected methods are:

  • router.push

  • router.replace

  • router.prefetch

潜在的解决方案

¥Potential solutions

import { useEffect } from 'react'
import { useRouter } from 'next/router'

// Here you would fetch and return the user
const useUser = () => ({ user: null, loading: false })

export default function Page() {
const { user, loading } = useUser()
const router = useRouter()

useEffect(() => {
// disable the linting on the next line - This is the cleanest solution
// eslint-disable-next-line no-floating-promises
router.push('/login')

// void the Promise returned by router.push
if (!(user || loading)) {
void router.push('/login')
}
// or use an async function, await the Promise, then void the function call
async function handleRouteChange() {
if (!(user || loading)) {
await router.push('/login')
}
}
void handleRouteChange()
}, [user, loading])

return <p>Redirecting...</p>
}

withRouter

如果 useRouter 不是最适合你,withRouter 也可以将相同的 router 对象 添加到任何组件。

¥If useRouter is not the best fit for you, withRouter can also add the same router object to any component.

用法

¥Usage

import { withRouter } from 'next/router'

function Page({ router }) {
return <p>{router.pathname}</p>
}

export default withRouter(Page)

TypeScript

要将类组件与 withRouter 一起使用,该组件需要接受 router 属性:

¥To use class components with withRouter, the component needs to accept a router prop:

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)