useRouter
如果你想在你的应用程序的任何函数组件中访问 router
对象,你可以使用 useRouter
Hook,请看以下示例
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
对象
以下是由 useRouter
和 withRouter
返回的 router
对象的定义
pathname
:String
- 当前路由文件的路径,位于/pages
之后。因此,不包括basePath
、locale
和尾部斜杠 (trailingSlash: true
)。query
:Object
- 查询字符串被解析为一个对象,包括 动态路由 参数。如果页面不使用 服务器端渲染,则在预渲染期间它将是一个空对象。默认为{}
asPath
:String
- 浏览器中显示的路径,包括搜索参数并遵循trailingSlash
配置。不包括basePath
和locale
。isFallback
:boolean
- 当前页面是否处于 fallback 模式。basePath
:String
- 活动的 basePath(如果已启用)。locale
:String
- 活动的 locale(如果已启用)。locales
:String[]
- 所有支持的 locales(如果已启用)。defaultLocale
:String
- 当前默认的 locale(如果已启用)。domainLocales
:Array<{domain, defaultLocale, locales}>
- 任何配置的域名 locales。isReady
:boolean
- 路由器字段是否已在客户端更新并准备好使用。应该只在useEffect
方法内部使用,而不是用于在服务器上有条件地渲染。有关与 自动静态优化页面 一起使用的用例,请参阅相关文档isPreview
:boolean
- 应用程序当前是否处于 预览模式。
如果页面使用服务器端渲染或 自动静态优化 渲染,则使用
asPath
字段可能会导致客户端和服务器之间不匹配。在isReady
字段为true
之前,避免使用asPath
。
以下方法包含在 router
中
router.push
处理客户端转换,此方法在 next/link
不够用时非常有用。
router.push(url, as, options)
url
:UrlObject | String
- 要导航到的 URL(有关UrlObject
属性,请参阅 Node.JS URL 模块文档)。as
:UrlObject | String
- 可选的装饰器,用于在浏览器 URL 栏中显示的路径。在 Next.js 9.5.3 之前,这用于动态路由。options
- 具有以下配置选项的可选对象scroll
- 可选的布尔值,控制导航后滚动到页面顶部。默认为true
shallow
:更新当前页面的路径,而无需重新运行getStaticProps
、getServerSideProps
或getInitialProps
。默认为false
locale
- 可选字符串,指示新页面的 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')}>
Click me
</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')}>
Click me
</button>
)
}
将用户重定向到 pages/login.js
,这对于需要 身份验证 的页面非常有用
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>
}
导航后重置状态
当在 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>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
之间导航不会重置 count。 useState
在渲染之间保持不变,因为顶级的 React 组件 Page
是相同的。
如果你不想要这种行为,你有几个选项
-
手动确保使用
useEffect
更新每个状态。在上面的示例中,它可能看起来像这样useEffect(() => { setCount(0) }, [router.query.slug])
-
使用 React
key
来 告诉 React 重新挂载组件。要对所有页面执行此操作,你可以使用自定义 apppages/_app.jsimport { 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 },
})
}}
>
Click here to read more
</button>
)
}
router.replace
与 next/link
中的 replace
属性类似,router.replace
将阻止向 history
堆栈添加新的 URL 条目。
router.replace(url, as, options)
router.replace
的 API 与router.push
的 API 完全相同。
看以下示例
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
会自动处理页面预取。
这是一个仅在生产环境中生效的功能。Next.js 不会在开发环境中预取页面。
router.prefetch(url, as, options)
url
- 要预取的 URL,包括显式路由(例如/dashboard
)和动态路由(例如/product/[id]
)as
-url
的可选装饰器。在 Next.js 9.5.3 之前,这用于预取动态路由。options
- 具有以下允许字段的可选对象locale
- 允许提供与活动 locale 不同的 locale。如果为false
,则url
必须包含 locale,因为不会使用活动 locale。
假设你有一个登录页面,在登录后,你将用户重定向到仪表板。对于这种情况,我们可以预取仪表板以实现更快的转换,如下例所示
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}>
{/* Form fields */}
<button type="submit">Login</button>
</form>
)
}
router.beforePopState
在某些情况下(例如,如果使用 自定义服务器),你可能希望监听 popstate 并在路由器对其执行操作之前执行某些操作。
router.beforePopState(cb)
cb
- 在传入的popstate
事件上运行的函数。该函数接收事件的状态,状态是一个具有以下属性的对象url
:String
- 新状态的路由。这通常是page
的名称as
:String
- 将在浏览器中显示的 urloptions
: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 }) => {
// 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()
。
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()
。
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 内部发生的各种事件。以下是受支持事件的列表
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(
`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} />
}
在此示例中,我们使用 自定义 App (
pages/_app.js
) 来订阅事件,因为它在页面导航时不会被卸载,但你可以在应用程序中的任何组件上订阅路由器事件。
路由器事件应在组件挂载时注册(useEffect 或 componentDidMount / componentWillUnmount)或在事件发生时命令式地注册。
如果路由加载被取消(例如,通过快速连续单击两个链接),则会触发 routeChangeError
。并且传递的 err
将包含一个设置为 true
的 cancelled
属性,如下例所示
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
导出
这是相同的 useRouter
Hook,但可以在 app
和 pages
目录中使用。
它与 next/router
的不同之处在于,当 pages 路由器未挂载时,它不会抛出错误,而是具有 NextRouter | null
的返回类型。这允许开发人员转换组件以支持在 app
和 pages
中运行,因为他们过渡到 app
路由器。
以前看起来像这样的组件
import { useRouter } from 'next/router'
const MyComponent = () => {
const { isReady, query } = useRouter()
// ...
}
当转换为 next/compat/router
时会报错,因为 null
无法被解构。相反,开发人员将能够利用新的 Hook
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])
// ...
}
此组件现在可以在 pages
和 app
目录中工作。当组件不再在 pages
中使用时,你可以删除对兼容性路由器的引用
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')
// ...
}
在 pages 目录的 Next.js 上下文之外使用 useRouter
另一个特定用例是在 Next.js 应用程序上下文之外渲染组件时,例如在 pages
目录上的 getServerSideProps
内部。在这种情况下,可以使用兼容性路由器来避免错误
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 错误
在 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'
// 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
对象 添加到任何组件。
用法
import { withRouter } from 'next/router'
function Page({ router }) {
return <p>{router.pathname}</p>
}
export default withRouter(Page)
TypeScript
要将类组件与 withRouter
一起使用,组件需要接受一个 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)
这个有帮助吗?