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 鉤子,這意味著它不能與類一起使用。你可以使用 withRouter 或將你的類包裝在函式元件中。
router 物件
以下是 useRouter 和 withRouter 返回的 router 物件的定義
pathname:String-/pages之後當前路由檔案的路徑。因此,不包括basePath、locale和尾部斜槓 (trailingSlash: true)。query:Object- 解析為物件的查詢字串,包括動態路由引數。如果頁面不使用伺服器端渲染,則在預渲染期間它將是一個空物件。預設為{}asPath:String- 瀏覽器中顯示的路徑,包括搜尋引數並遵循trailingSlash配置。不包括basePath和locale。isFallback:boolean- 當前頁面是否處於回退模式。basePath:String- 活動的 basePath(如果已啟用)。locale:String- 活動的語言環境(如果已啟用)。locales:String[]- 所有支援的語言環境(如果已啟用)。defaultLocale:String- 當前預設語言環境(如果已啟用)。domainLocales:Array<{domain, defaultLocale, 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- 可選布林值,控制導航後是否滾動到頁面頂部。預設為trueshallow: 更新當前頁面的路徑,而不重新執行getStaticProps、getServerSideProps或getInitialProps。預設為falselocale- 可選字串,指示新頁面的語言環境
你不需要為外部 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 之間導航**不會**重置計數。useState 在渲染之間保持不變,因為頂層 React 元件 Page 是相同的。
如果你不想要這種行為,你有幾個選項
-
手動確保使用
useEffect更新每個狀態。在上面的示例中,這可能看起來像useEffect(() => { setCount(0) }, [router.query.slug]) -
使用 React
key告訴 React 重新掛載元件。要對所有頁面執行此操作,可以使用自定義應用程式pages/_app.jsimport { useRouter } from 'next/router' export default function MyApp({ Component, pageProps }) { const router = useRouter() return <Component key={router.asPath} {...pageProps} /> }
使用 URL 物件
你可以像用於 next/link 一樣使用 URL 物件。適用於 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- 允許提供與活動語言環境不同的語言環境。如果為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({
/* 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 路由器中發生的各種事件。以下是支援的事件列表
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} />
}我們在此示例中使用自定義應用程式(
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 鉤子相同,但可以在 app 和 pages 目錄中使用。
它與 next/router 的不同之處在於,當 pages 路由器未掛載時,它不會丟擲錯誤,而是返回 NextRouter | null 型別。這允許開發人員在轉換為 app 路由器時將元件轉換為支援在 app 和 pages 中執行。
以前看起來像這樣的元件
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() // 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')
// ...
}在 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.pushrouter.replacerouter.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 一起使用,元件需要接受一個路由器屬性
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)這有幫助嗎?