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
物件
以下是 useRouter
和 withRouter
返回的 router
物件的定義:
pathname
:String
- 當前路由檔案的路徑,位於/pages
之後。因此不包含basePath
、locale
和結尾斜線 (trailingSlash: true
)。query
:Object
- 解析為物件的查詢字串,包含動態路由參數。如果頁面未使用伺服器端渲染 (SSR),則在預渲染時會是空物件。預設為{}
asPath
:String
- 瀏覽器中顯示的路徑,包含搜尋參數並遵循trailingSlash
設定。不包含basePath
和locale
。isFallback
:boolean
- 當前頁面是否處於後備模式 (fallback mode)。basePath
:String
- 當前啟用的 basePath(如果已啟用)。locale
:String
- 當前啟用的語言環境 (locale)(如果已啟用)。locales
:String[]
- 所有支援的語言環境(如果已啟用)。defaultLocale
:String
- 當前預設的語言環境(如果已啟用)。domainLocales
:Array<{domain, defaultLocale, locales}>
- 任何已設定的網域語言環境。isReady
:boolean
- 路由欄位是否已在客戶端更新並準備就緒。應僅在useEffect
方法中使用,不用於伺服器端的條件渲染。相關使用案例請參閱自動靜態優化頁面的文件。isPreview
:boolean
- 應用程式是否處於預覽模式 (preview mode)。
如果頁面使用伺服器端渲染或自動靜態優化,使用
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
- 可選字串,表示新頁面的語言環境
對於外部 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
,適用於需要身份驗證 (authentication) 的頁面:
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>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.js import { 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
會阻止將新的 URL 條目添加到 history
堆疊中。
router.replace(url, as, options)
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')}>
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({
/* 表單資料 */
}),
}).then((res) => {
// 快速客戶端過渡到已預取的儀表板頁面
if (res.ok) router.push('/dashboard')
})
}, [])
useEffect(() => {
// 預取儀表板頁面
router.prefetch('/dashboard')
}, [router])
return (
<form onSubmit={handleSubmit}>
{/* 表單欄位 */}
<button type="submit">Login</button>
</form>
)
}
router.beforePopState
在某些情況下(例如使用自訂伺服器 (Custom Server)),您可能希望監聽 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 }) => {
// 我只想允許這兩個路由!
if (as !== '/' && as !== '/other') {
// 讓 SSR 將錯誤路由渲染為 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 })
- 雜湊 (hash) 將變更但頁面不變時觸發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)
// 如果元件卸載,使用 `off` 方法取消訂閱事件:
return () => {
router.events.off('routeChangeStart', handleRouteChange)
}
}, [router])
return <Component {...pageProps} />
}
我們在此範例中使用自訂應用程式 (Custom 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)
// 如果元件卸載,使用 `off` 方法取消訂閱事件:
return () => {
router.events.off('routeChangeError', handleRouteChangeError)
}
}, [router])
return <Component {...pageProps} />
}
next/compat/router
匯出
這是相同的 useRouter
鉤子 (hook),但可以在 app
和 pages
目錄中使用。
它與 next/router
的不同之處在於,當頁面路由器 (pages router) 未掛載時不會拋出錯誤,而是返回類型為 NextRouter | null
。這讓開發者在過渡到 app
路由器 (router) 時,可以轉換元件以支援在 app
和 pages
中運行。
原本像這樣的元件:
import { useRouter } from 'next/router'
const MyComponent = () => {
const { isReady, query } = useRouter()
// ...
}
轉換為 next/compat/router
時會出錯,因為 null
無法被解構 (destructure)。相反地,開發者可以利用新的鉤子:
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
中使用時,可以移除對相容路由器 (compat router) 的引用:
import { useSearchParams } from 'next/navigation'
const MyComponent = () => {
const searchParams = useSearchParams()
// 由於此元件僅在 `app/` 中使用,可以移除相容路由器。
const search = searchParams.get('search')
// ...
}
在 Next.js 上下文之外使用 useRouter
(pages 目錄)
另一個特定用例是在 Next.js 應用程式上下文之外渲染元件,例如在 pages
目錄中的 getServerSideProps
內部。在這種情況下,可以使用相容路由器來避免錯誤:
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(() => {
// 在下一行禁用 linting - 這是最簡潔的解決方案
// eslint-disable-next-line no-floating-promises
router.push('/login')
// void router.push 返回的 Promise
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>Redirecting...</p>
}
withRouter
如果 useRouter
不適合你,withRouter
也可以將相同的 router
物件 添加到任何元件中。
使用方法
import { withRouter } from 'next/router'
function Page({ router }) {
return <p>{router.pathname}</p>
}
export default withRouter(Page)
TypeScript
要將類別元件 (class components) 與 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)