Nuxt.js 2.x 實戰手冊(5) - 生命週期(lifecycle)

前言

接下來要聊一下 Nuxt.js 中很重要的東西,也就是生命週期,而這個生命週期是會大大影響你的 SSR 結果與 SEO 的。

生命週期

首先在 Nuxt.js 中有以下生命週期

  • nuxtServerInit - 伺服器初始化
  • middleware - 中間組件
  • validate - 驗證參數
  • asyncData - 非同步資料處理
  • fetch - 非同步資料處理

請注意以上生命週期是 Nuxt.js 所提供的,與你所認知中的 Vue 生命週期是不同的東西,Vue 本身是沒有以上這些生命週期唷。

除此之外,你也必須額外 this 在以下生命週期是無法被呼叫,你只會得到一個 undefined

  • nuxtServerInit - 伺服器初始化
  • middleware - 中間組件
  • validate - 驗證參數
  • asyncData - 非同步資料處理

因為以上這幾個生命週期階段都是還在 Server 上,因此並不存在 this

nuxtServerInit (伺服器初始化)

nuxtServerInit 主要運作於 Nuxt.js 環境初始化時才會觸發的生命週期,而你可以在這個階段初始化 Vuex。

1
2
3
4
5
export default {
nuxtServerInit (store, content) {
console.log('nuxtServerInit');
},
}

middleware (中間組件)

middleware 分別有 Global、Layout、Page,但是這邊要注意一下 middleware 的執行順序,Nuxt.js 中 middleware 執行順序是 Global → Layout → Page,這件事情非常重要請務必記得。

那麼什麼是 Global middleware 呢?你可以試著在 Nuxt.js 專案下建立一個 middleware 資料夾,並建立一個檔案叫做 global.js,內容如下:

1
2
3
export default ({ store, route, redirect, params, query, req, res }) => {
console.log('Global middleware');
}

然後到 nuxt.config.js 的 route 屬性新增這個 middleware:

1
2
3
4
5
export default {
router: {
middleware: ['global']
},
}

接下來到 Layout/default.vue 增加以下:

1
2
3
4
5
export default {
middleware ({ store, route, redirect, params, query, req, res }) {
console.log('Layout middleware')
}
}

最後你可以到任何一個頁面(Page)寫一個 middleware 函式:

1
2
3
4
5
export default {
middleware ({ store, route, redirect, params, query, req, res }) {
console.log('Page middleware')
}
}

最後你可以到新增 Page middleware 的頁面看 console 是如何呈現的

middleware

這邊提醒一下 Page middleware 也可以直接指向 middleware 資料夾的檔案,只需要改寫成以下:

1
2
3
export default {
middleware: 'global',
}

但依然要注意執行順序並不會因此改變。

validate (驗證參數)

validate 可以做一些驗證

1
2
3
4
5
6
export default {
validate (content) {
console.log('validate')
return true
},
}

如果你切換頁面後想要驗證一些 router 的參數的話,那麼就可以使用這個生命週期,但是請務必注意這個生命週期必須回傳 true or false

asyncData (非同步資料處理)

asyncData 可以說是實戰上最常使用的生命週期,也是 Nuxt.js SSR 的精髓重點生命週期。

1
2
3
4
5
export default {
asyncData () {
console.log('asyncData')
},
}

為什麼會說是 Nuxt.js 的精髓呢?在前面章節我們有提到 SPA 本身就是靠 JavaScript 生成 DOM 的,而因為這個關係在 SEO 上就會非常差,因為搜尋引擎爬蟲並沒有辦法取得內容,那麼只要你將需要生成在畫面上的資料放在 asyncData 中並回傳給 Nuxt.js 那麼就會被真實的渲染在畫面上。

如果要在 asyncData 中使用 axios 的話,只需要這樣寫即可:

1
2
3
4
5
export default {
asyncData ({ $axios }) {
console.log('asyncData')
},
}

這邊我就使用最知名的 Random User Generator API 來模擬 AJAX 之後將資料真實鉉在畫面上:

1
2
3
4
5
6
7
8
export default {
async asyncData ({ $axios }) {
const res = await $axios.get('https://randomuser.me/api/')
return {
data: res.data.results
}
},
}

這樣寫之後,你就可以渲染在畫面上:

1
2
3
4
5
6
7
8
9
<template>
<div>
<ul>
<li v-for="item in data" :key="item.email">
{{ item.email }}
</li>
</ul>
</div>
</template>

接下來你可以在瀏覽器畫面上點右鍵「檢視原始碼」,這時候你是真的可以看到 AJAX 被渲染在原始碼上

檢視原始碼

因為資料是真實存在於 HTML 上的關係,爬蟲就可以取得你的資料內容。

fetch (非同步資料處理)

fetch 其實跟 asyncData 是一樣的東西:

1
2
3
4
5
export default {
fetch () {
console.log('fetch')
},
}

因此你也可以在這個生命週期做 AJAX 取得,但是這個生命週期比較特別的地方在於,你可以在這個生命週期下使用 this 指向到 Vue 的 data,如果是這個生命週期之外的,例如:asyncDatavalidate 等等是不行的,因此你可以把 fetch 想像成是 Nuxt.js 跟 Vue.js 的中間溝通橋樑,通常來講 fetch 會放在 data() 之後:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div>
<ul>
<li v-for="item in users" :key="item.email">
{{ item.email }}
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
users: []
}
},
async fetch () {
const res = await this.$axios.get('https://randomuser.me/api/')
this.users = res.data.results
},
}
</script>

因為它依然是會先在伺服器運作,而這一段也會跟 asyncData 相同被渲染在原始碼上。

最後要注意一下 fetch 有一個雷點就是不要解構,例如:

1
2
3
4
5
6
export default {
async fetch ({ $axios }) {
const res = await $axios.get('https://randomuser.me/api/')
this.users = res.data.results
},
}

如果你這樣寫的話,雖然程式碼不會出現任何警告訊息,但你的資料會無法正常渲染到畫面上。

asyncData 與 fetch 的選擇

這時候應該會很疑惑 asyncDatafetch 的使用時機,其實非常簡單,如果你拉回遠端資料後,需要被 Vue 所使用或者是指向給 Vue 的話,那就使用 fetch,若沒有的話,基本上使用 asyncData 就可以了。

最後這邊附上 Nuxt.js 官方所提供的生命週期圖:

生命週期(lifecycle)

參考文獻