看了就會的next.js路由

前言

本文將會介紹next.js的路由相關內容,做爲一個新手,筆者自知文章並無太多深層次的東西,只但願幫助到跟筆者同樣的入門級小夥伴。本文基礎爲上一篇next.js+koa2+antd環境輕鬆搭建javascript

next.js目錄結構

在介紹路由以前,想先簡單說一下目錄結構,其中有些東西對於路由講解仍是頗有幫助的css

├── .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就不說了。vue

pagescomponents pages是next.js中很是重要的一個目錄,其中每個js文件就表明一個頁面,可是有兩個例外,一個是上一篇文章中用到的_app.js,一個是_document.js。咱們在pages下再建立一個a.js和test/b.js,而後看看效果java

// 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.jsnav.js兩個組件,在pages/indx.js中將其引入並使用react

.next: 在咱們運行過next.js項目以後,會自動生成.next目錄,這裏存放的內容是next.js將咱們寫的pages和components等源碼打包編譯後的結果,咱們經過瀏覽器訪問能夠拿到的內容其實就來自這裏,在後續進行上限打包時候也會生成這個目錄.git

Tips:請不要修改這個目錄中的內容express

其餘 staic目錄存放靜態文件,好比圖片,css,favicon.ico等 .babelrc文件存放babel配置 next.config.js存放next的配置 server.js是咱們上一篇寫Koa服務器程序的代碼npm

next.js路由

Link組件

咱們能夠刪除index.js中的全部內容,重寫爲:json

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路由的方式其實都是在本地瀏覽器環境建立的一個路由,而服務端並不知情,若是這個路由服務端並不存在,那麼就會拿到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錯誤,所以要藉助後端框架實現路由映射,最後咱們提到了路由鉤子。但願這篇文章對小夥伴們有用,感謝閱讀。

相關文章
相關標籤/搜索