看了就會的next.js路由

king帥帥發表於2019-04-21

前言

本文將會介紹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就不說了。

pagescomponents 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路由
我們可以發現next.js會將pages下的js檔案根據其路徑名和檔名自動生成對應的路由。

但是我們再寫頁面的時候不可能將所有東西都放在pages下,我們可以將具體內容作為元件放在components目錄中,然後在pages中相應的js檔案中引入。如果是用腳手架工具生成專案的小夥伴可以很直觀的看到components目錄中有head.jsnav.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頁面也不會有重新整理的情況。

看了就會的next.js路由
還有一點需要說明的是,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路由

路由對映

上面說過next.js中沒有引數路由,只能通過/test?id=xxx來傳遞引數,但是我們就是想用引數路由怎麼辦,雖然我們做不到,但是可以模擬一下,通過/test/xxx來傳遞引數

寫法:

<Link href="/a?id=1" as="/a/1">
    <Button>跳轉到A</Button>
</Link>
複製程式碼

看了就會的next.js路由
是不是更優雅了,只不過我們這裡寫死了而已

Router.push()的寫法:

Router.push({
  pathname: '/test/b',
  query: {
    id: 2
  }
}, '/test/b/2')
複製程式碼

達到的效果是一樣的

請注意 我們將http://localhost:3000/a/1複製在新標籤頁開啟,會出現404

看了就會的next.js路由

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

看了就會的next.js路由
我們通過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按鈕,輸出以下內容

看了就會的next.js路由
我們分析一下:點選跳轉按鈕以後,路由即將跳轉,在跳轉前觸發routeChangeStart鉤子,輸出routeChangeStart,以及對應...args引數,然後此時瀏覽器的路由歷史會發生改變,因為跳轉後前一個歷史就是我們的localhost:3000這個頁面,在歷史發生改變的前一刻觸發beforeHistoryChange,然後路由進行跳轉,結束後觸發routeChangeComplete路由更改完成的鉤子。

hash路由也是一樣的:相對來說hash路由在本頁跳轉不會更改歷史,所以我們將會看到這樣的效果

看了就會的next.js路由
這個實驗怎麼做呢?我們可以去更改/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錯誤,因此要藉助後端框架實現路由對映,最後我們提到了路由鉤子。希望這篇文章對小夥伴們有用,感謝閱讀。

相關文章