看了就會的next.js路由
在介紹路由之前,想先簡單說一下目錄結構,其中有些東西對於路由講解還是很有幫助的
├── .next │├── build-manifest.json │├── react-loadable-manifest.json │├── server │└── static ├── components │├── head.js │└── nav.js ├── pages │├── _app.js │└── index.js ├── static │└── favicon.ico ├── server.js ├── .babelrc ├── .gitignore ├── next.config.js ├── package.json ├── README.md └── yarn.lock 複製程式碼
這是 next.js+koa2+antd環境輕鬆搭建 一文中建立的next+koa2+antd的檔案目錄,其中的 README.md
, package.json
, yarn.lock
, .gitignore
就不說了。
pages
和 components
pages
是next.js中非常重要的一個目錄,其中每一個js檔案就代表一個頁面,但是有兩個例外,一個是上一篇文章中用到的 _app.js
,一個是 _document.js
。我們在pages下再建立一個a.js和test/b.js,然後看看效果
// a.js export default () => <div>this is a page</div> // test/b.js export default () => <div>this is b page</div> 複製程式碼

我們可以發現next.js會將pages下的js檔案根據其路徑名和檔名自動生成對應的路由。
但是我們再寫頁面的時候不可能將所有東西都放在pages下,我們可以將具體內容作為元件放在 components
目錄中,然後在 pages
中相應的js檔案中引入。如果是用腳手架工具生成專案的小夥伴可以很直觀的看到 components
目錄中有 head.js
和 nav.js
兩個元件,在 pages/indx.js
中將其引入並使用
.next:在我們執行過next.js專案之後,會自動生成 .next
目錄,這裡存放的內容是next.js將我們寫的pages和components等原始碼打包編譯後的結果,我們通過瀏覽器訪問可以拿到的內容其實就來自這裡,在後續進行上限打包時候也會生成這個目錄.
Tips:請不要修改這個目錄中的內容
其他 staic
目錄存放靜態檔案,比如圖片,css,favicon.ico等 .babelrc
檔案存放babel配置 next.config.js
存放next的配置 server.js
是我們上一篇寫Koa伺服器程式的程式碼
next.js路由
Link
元件
我們可以刪除index.js中的所有內容,重寫為:
import Link from 'next/link' //引入Link元件 import { Button } from 'antd' //引入antd中的Button元件 export default () => { return ( <Link href="/a"> <Button>跳轉到A</Button> </Link> ) } 複製程式碼
這裡我們引入Link元件並將Button包裹在Link元件中,Link的 href
屬性可以讓我們選擇跳轉到的路由,這裡我們 /a
是跳轉到我們上文中建立的a.js對應的頁面
值得說明的是,Link的機制並不是在Button上增加了a標籤實現的跳轉,而是對其中的元件添加了click事件,然後在瀏覽器中建立一個路由,在最終渲染的結果上並不會對Button包裹任何元素,我們也可以看到點選Button頁面也不會有重新整理的情況。

React.Children.only
規定了它所包含的元素只能有一個,如果我們再並列Button寫一個標籤就會報錯
<Link href="/a"> <Button>跳轉到A</Button> <Button>也跳轉到A</Button>//報錯 </Link> 複製程式碼
如果我們有這種需求,可以將兩個Button包裹在 <div>
中
<div> <Button>跳轉到A</Button> <Button>也跳轉到A</Button> </div> 複製程式碼
此時點選兩個按鈕都會跳轉到a頁面,(知道事件冒泡的小夥伴可能對為什麼都能跳轉到a頁面,不清楚的小夥伴可以去查查事件冒泡和捕獲)
next.js中的Router物件
next.js為我們提供的並不只是 Link
元件,它還為我們提供了Router的方式 Tips: 這裡的Router不是一個元件而是一個路由物件,Link的實現原理也是基於Router物件
我們先來看看程式碼:
import Link from 'next/link' import Router from 'next/router' // 新引入進來的 import { Button } from 'antd' export default () => { const goToB = () => { Router.push('/test/b') } return ( <> <Link href="/a"> <Button>跳轉到A</Button> </Link> <Button onClick={goToB} >跳轉到B</Button> // 新增的一個Button元件 </> ) } 複製程式碼
我們可以看到新增了一個Button元件,它有一個 goToB
的onClick事件,點選之後會執行 goToB
函式。 goToB
做了什麼呢?它給Router添加了一個路由 /test/b
對應我們之前 /test/b.js
渲染的內容,此時我們就可以跳轉到B頁面了,是不是很簡單。
動態路由
在next.js中,無法通過 /test/:id
這種引數路由的方式獲取到引數,它只能通過 query
的方式獲取引數,即 /test?id=xx
的方式
寫法:
<Link href="/a?id=1"> <Button>跳轉到A</Button> </Link> 複製程式碼
使用Router的方式也可以通過問號這種形式來寫,不過還可以通過給 Router.push()
傳遞一個物件來寫
const goToB = () => { Router.push({ pathname: '/test/b', query: { id: 2 } }) } 複製程式碼
此時對應a頁面的內容要獲得傳遞過來的id=1,就得稍微改寫一下:
import { withRouter } from 'next/router' //新引入的 const A =({ router }) => <div>this is a page,引數是{router.query.id}</div> export default withRouter(A) 複製程式碼
我們引入了 withRouter
元件,它是一個高階元件(HOC),即引數為被包裹的元件,返回值也是一個元件(不瞭解的小夥伴可以去看看react文件中高階元件的部分)。
我們這裡不直接匯出A元件了,而是匯出withRouter包裹後的元件,並且給A元件傳入了router引數,通過router.query.id獲取傳遞過來的id。 我們可以看看效果

路由對映
上面說過next.js中沒有引數路由,只能通過 /test?id=xxx
來傳遞引數,但是我們就是想用引數路由怎麼辦,雖然我們做不到,但是可以模擬一下,通過 /test/xxx
來傳遞引數
寫法:
<Link href="/a?id=1" as="/a/1"> <Button>跳轉到A</Button> </Link> 複製程式碼

是不是更優雅了,只不過我們這裡寫死了而已
Router.push()的寫法:
Router.push({ pathname: '/test/b', query: { id: 2 } }, '/test/b/2') 複製程式碼
達到的效果是一樣的
請注意我們將http://localhost:3000/a/1複製在新標籤頁開啟,會出現404

為什麼會出現這種情況?有沒有小夥伴記得上文提到過,上文中說過一句話 然後在瀏覽器中建立一個路由

我們通過next.js路由的方式其實都是在本地瀏覽器環境建立的一個路由,而服務端並不知情,如果這個路由服務端並不存在,那麼就會拿到404 not found。
此處不知道有沒有小夥伴會問:既然next.js路由跳轉都是在本地瀏覽器建立的路由,那為什麼通過Button點選進入的 localhost:3000/a
複製之後在新標籤頁開啟卻是正常的?這是因為伺服器端我們確實存在 pages/a.js
啊,此處剛好瀏覽器端認識 /a
路由,但是 /a/1
加上id後瀏覽器端就沒定義過這個 pages/a/1.js
檔案,所以什麼都匹配不到
那這種情況該怎麼處理,請不要擔心,我們安裝的koa會提供全面的路由,我們只需要做個路由對映就好了,使用express或者egg.js也是同樣的道理。
首先我們執行 npm install koa-router --save
或者 yarn add koa-router
(筆者使用的是yarn)安裝koa路由中介軟體 在server.js中引入並使用
/* * @Author: yishuai * @Date:2019-04-21 09:57:53 * @Last Modified by:yishuai * @Last Modified time: 2019-04-21 12:22:22 */ const Koa = require('koa') const Router = require('koa-router') // 引入路由 const next = require('next') const dev = process.env.NODE_ENV !== 'production' const app = next({ dev }) const handle = app.getRequestHandler() app.prepare().then(() => { const server = new Koa() const router = new Router() // 定義路由 // 設定路由,與next.js的路由進行對映 router.get('/a:id', async (ctx) => { // handle傳入的第三個引數跟我們next.js中用Router.push({})傳入的陣列一樣 await handle(ctx.req, ctx.res, { pathname: '/a', query: { id } }) ctx.respond = false }) // 使用路由 server.use(router.routes()) server.use(async (ctx, next) => { await handle(ctx.req, ctx.res) ctx.respond = false }) server.listen(3000, () => { console.log('server is running at http://localhost:3000') }) }) 複製程式碼
具體對映方式在上方程式碼的註釋中,就不詳細說了不熟悉koa的小夥伴去看看koa的文件,瞭解一下koa路由的使用。 然後開啟瀏覽器新建頁面重新輸入 localhost:3000/a/1
就可以正常訪問了
next.js中的路由鉤子
這裡說的鉤子,相信大多數了解過react或vue生命週期的小夥伴都知道生命週期鉤子,這些鉤子函式在一定條件下會自動執行,我們如果在某些生命週期過程中有自定義的需求,可以藉助生命鉤子函式來完成。比如react中的 componentDidMount(){xxxxxx}
可以在元件載入完成後做 xxxxxxxx
事情,比如當元件載入完成後我們要驗證使用者是不是登入了,就可以把 xxxxxxx
替換成我們的驗證邏輯。
這裡的路由鉤子也是一樣的道理,當路由被觸發的時候就會在路由跳轉前,跳轉後等時間節點自動執行一些函式,這些函式就是路由鉤子
我在index.js中 添加了
(不是重寫index.js)這樣一段程式碼:
// 所有的路由鉤子名稱,寫在了一個數組中 const events = [ 'routeChangeStart', 'routeChangeComplete', 'routeChnageError', 'beforeHistoryChange', 'hashChangeStart', 'hashChangeComplete' ] // 通過一個高階函式在鉤子觸發後執行自定義的邏輯,這裡直接輸出了鉤子名稱和鉤子函式的引數 function makeEvent(type) { return (...args) => { console.log(type, ...args) } } //通過forEach遍歷 繫結鉤子事件 events.forEach(event => { Router.events.on(event, makeEvent(event)) }) 複製程式碼
上面註釋中說明了一些情況,我們可以通過events名稱直觀地看到next的鉤子總共有六種分別是:路又開始時候,路由完成時候,路由出錯時候,路由歷史更改之前,雜湊路由開始時候,雜湊留有完成時候這6個時間節點會觸發相應的路由鉤子。
中間用了一個高階函式,可能有些小夥伴會有點看不懂,這裡簡單介紹一下:
高階函式簡單講就是一個函式作為另一個函式的引數,或者一個函式作為另一個函式的返回值。
如果說我們只想繫結 routeChangeStart
事件,則可以這樣寫,當觸發routeChangeStart時候,輸出一個內容。
Router.events.on('routeChangeStart', function(...args){ console.log('routeChangeStart',...args) }) 複製程式碼
我們這裡為了方便使用了forEach迴圈了上述程式碼,傳遞的 Router.events.on()
第二個引數要接收一個函式,我們就可以利用高階函式,執行高階函式後剛好返回一個函式,就用來做它的第二個引數,相信到這裡再看上面的程式碼就清晰很多了。
我們綁定了所有的鉤子,可以去看看效果:點選 跳轉到A
按鈕,輸出以下內容

routeChangeStart
鉤子,輸出routeChangeStart,以及對應
...args
引數,然後此時瀏覽器的路由歷史會發生改變,因為跳轉後前一個歷史就是我們的
localhost:3000
這個頁面,在歷史發生改變的前一刻觸發
beforeHistoryChange
,然後路由進行跳轉,結束後觸發
routeChangeComplete
路由更改完成的鉤子。
hash路由也是一樣的:相對來說hash路由在本頁跳轉不會更改歷史,所以我們將會看到這樣的效果

這個實驗怎麼做呢?我們可以去更改/pages/a.js,引入Link元件,給原來返回的內容包裹上Link和a標籤
import { withRouter } from 'next/router' import Link from 'next/link' // 新引入的 // 外層加了個a標籤和Link標籤,Link標籤跳轉到hash路由#hello const A =({ router }) => <Link href="#hello"><a><div>this is a page,引數是{router.query.id}</div></a></Link> export default withRouter(A) 複製程式碼
結束
好了,到此next.js的路由我們就有了一定的認識。感謝小夥伴堅持到最後。
next.js的路由相對後端框架或者是react-router都要簡單很多,本質上就只是pages路徑下對應的js檔案的目錄和檔名稱直接作為路由,然後就是支援了query傳遞引數,路由跳轉是在本地瀏覽器上操作的,所以如果不借助外力,可能會有404錯誤,因此要藉助後端框架實現路由對映,最後我們提到了路由鉤子。希望這篇文章對小夥伴們有用,感謝閱讀。