前言
本文將會介紹next.js的路由相關內容,作為一個新手,筆者自知文章並沒有太多深層次的東西,只希望幫助到跟筆者一樣的入門級小夥伴。本文基礎為上一篇next.js+koa2+antd環境輕鬆搭建
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頁面也不會有重新整理的情況。
還有一點需要說明的是,Link通過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路由跳轉都是在本地瀏覽器建立的路由,那為什麼通過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錯誤,因此要藉助後端框架實現路由對映,最後我們提到了路由鉤子。希望這篇文章對小夥伴們有用,感謝閱讀。