# Vue3 - 進階篇(Nuxt)
###### tags: `Vue` `Nuxt` `HiSKIO`
## SPA v.s. SSR
### SPA (Single Page Application,單頁式應用)
所有畫面由前端 JS 操控:HTML 渲染,讀到 .js檔案 後再把相對應的 html 丟到畫面上。
只有一個頁面,用起來卻像 app 一樣。
右鍵 -> 檢視網頁原始碼 -> 內容幾乎是空的。
只更新部分畫面,而不是暴力的每次都砍掉重練。
#### 優點
1. 使用者體驗佳 (利用 ajax,著名例子:Gmail)
2. 必要場景 (例如:音樂播放網站,不會因為換頁而停掉音樂)
#### 缺點
1. SEO 差:
因為 index.html 幾乎是空的,google 爬蟲效果有待討。
網頁初始畫面在 API 回來之前不存在有意義的資料,搜尋引擎爬蟲抓不到網頁的內容。
3. JS 打包檔大
### SSR (Server Side Rendering,伺服器端渲染)
所有畫面為伺服器端收到請求後,解析出完整的 HTML 後,再傳給使用者端。
#### 優點
1. SEO 佳
2. 首頁加載速度快
#### 缺點
1. 使用者體驗差
:::info
綜合兩者 SSR + SPA:
第一個頁面由 Server side render,之後的操作還是由 Client side render
:::
:::success
MVC 就是因為 code 變得越來越亂,所以將職責區分清楚的一種設計模式。SPA 就是因為想增進使用者體驗,而出現的一種在前端利用 Ajax 達成不換頁的方法。SSR 就是因為要解決 SPA 的 SEO 問題而出現的解法。
:::
:::warning
看使用情境決定決定使用哪種方式:
例如:音樂播放網站(不換頁)、後台系統網站(不需要 SEO),就適合使用 SPA;反之,產品網站(需要 SEO),較適合使用 SSR。
:::
### 參考資料
* [跟著小明一起搞懂技術名詞:MVC、SPA 與 SSR. 這篇的靈感來自於 Front-End Developers Taiwan… | by Huli | Medium](https://hulitw.medium.com/introduction-mvc-spa-and-ssr-545c941669e9)
* [前後端分離與 SPA](https://blog.techbridge.cc/2017/09/16/frontend-backend-mvc/)
* [淺述 SSR SPA 優缺點 « Nic Lin's Blog](https://blog.niclin.tw/2019/01/06/%E6%B7%BA%E8%BF%B0-ssr-spa-%E5%84%AA%E7%BC%BA%E9%BB%9E/)
* [對於 SSR 的思考與使用場景 | 半熟前端](https://blog.kalan.dev/2020-11-23-rethink-ssr/)
## What is Nuxt ?
詳見官網介紹:[Nuxt.js - The Intuitive Vue Framework](https://nuxtjs.org/)
Nuxt.js 是一個基於 Vue.js 的通用應用框架。
1. SERVER SIDE RENDERED:預設利用 Vue.js 開發伺服器端渲染(SSR)的應用所需要的各種配置。
2. STATICALLY GENERATED:提供了一種命令叫:`nuxt generate`,為基於 Vue.js 的應用提供生成對應的靜態站點的功能。
## How Nuxt Run ?
Nuxt.js 是建構在 Node.js 環境之上。
Nuxt 將專案打包成兩份,一份是給 Client 端,一份是給 Server 端。
![](https://i.imgur.com/TI2ffVR.png)
## Install Nuxt
```
npx create-nuxt-app <project-name>
```
### npm v.s npx
npm:是套件管理工具,可以把想要的 node 套件,透過 npm 安裝在 local 專案位置或是 global 全局性整台電腦的環境下。
npx:npx 是在 npm v5.2.0 之後內建的指令,也是一種安裝工具,讓我們可以更方便的安裝或是管理依賴(dependencies)。
#### 差別一:npm 永久安裝 v.s. npx 安裝後即移除
#### 差別二:npx 更方便的運行本地套件
#### 差別三:npx 直接執行 Github 檔案
#### 差別四:npx 可以指定 node 版本、命令的版本
主要特點:
1. 臨時安裝可執行依賴包,不用全局安裝,不用擔心長期的污染。
2. 可以執行依賴包中的命令,安裝完成自動運行。
3. 自動加載 node_modules 中依賴包,不用指定$PATH。
4. 可以指定 node 版本、命令的版本,解決了不同項目使用不同版本的命令的問題。
參考資料:
* [[NodeJS] npx 是什麼? 跟 npm 差在哪?. npx? npm? What’s the difference? | by itsems | itsems_frontend | Medium](https://medium.com/itsems-frontend/whats-npx-e83400efe7f8)
* [npx和npm之间的关系](https://juejin.cn/post/6844903945664462855)
![](https://i.imgur.com/WT2Zu7B.png)
## Nuxt 架構解析
[Directory Structure - NuxtJS](https://nuxtjs.org/docs/2.x/get-started/directory-structure)
### pages & layouts & components: `.vue`檔
### pages
* [Pages directory - NuxtJS](https://nuxtjs.org/docs/2.x/directory-structure/pages)
* 為頁面的內容(views),會自動產生 `router`。
#### 動態頁面
:question: [Pages directory - NuxtJS](https://nuxtjs.org/docs/2.x/directory-structure/pages#dynamic-pages)
### layouts
* [Layouts directory - NuxtJS](https://nuxtjs.org/docs/2.x/directory-structure/layouts)
#### 預設頁面 Default Layout
* 為所有頁面所共用。其中 `<Nuxt />` 即為 `pages` 中的 `.vue`檔內容 (很像 Vue CLI 中的 `router-view` )
```htmlmixed=
<template>
<div>
<TheHeader />
<Nuxt />
<TheFooter />
</div>
</template>
```
#### 客製化頁面 Custom Layout
* 為特定頁面共用相同頁面
例如:
`layouts/blog.vue`
```htmlmixed=
<template>
<div>
<div>My blog navigation bar here</div>
<Nuxt />
</div>
</template>
```
在 `pages` component 中指定要呈現的 `layouts`
`pages/posts.vue`
```htmlmixed=
<script>
export default {
layout: 'blog',
// OR
layout (context) {
return 'blog'
}
}
</script>
```
#### 錯誤頁面 Error Page
當有錯誤(e.g. 404, 500)發生時呈現的頁面。
:::warning
雖然該檔案位於 `layouts` 之下,但屬於 `pages` components!
因此不會有 `<Nuxt />` 在其中。
:::
`layouts/error.vue`
```htmlmixed=
<template>
<div class="container">
<h1 v-if="error.statusCode === 404">Page not found</h1>
<h1 v-else>An error occurred</h1>
<NuxtLink to="/">Home page</NuxtLink>
</div>
</template>
<script>
export default {
props: ['error'],
layout: 'blog' // you can set a custom layout for the error page
}
</script>
```
### components
* [Components directory - NuxtJS](https://nuxtjs.org/docs/2.x/directory-structure/components)
* 放所有共用的元件。
* 自 `v2.13,`以後可以自動引入進任何 `pages`,`layouts` 或 `components` 中,無需手動 import。
在 `nuxt.config.js` 中設定:
```javascript=
export default {
components: true
}
```
詳細設定請見官網
#### 動態載入 Dynamic Imports
動態載入即所謂的懶加載。只要在 template 中加入 `Lazy` 前綴詞。
`layouts/default.vue`
```htmlmixed=
<template>
<div>
<TheHeader />
<Nuxt />
<LazyTheFooter />
</div>
</template>
```
當事件被觸發時,動態載入元件。
`pages/index.vue`
```htmlmixed=
<template>
<div>
<h1>Mountains</h1>
<LazyMountainsList v-if="show" />
<button v-if="!show" @click="show = true">Show List</button>
</div>
</template>
<script>
export default {
data() {
return {
show: false
}
}
}
</script>
```
#### 巢狀路徑 Nested Directories
若元件放置位置如下:
```
components/
base/
foo/
Button.vue
```
則該元件的名稱則包含其路徑位置,即為 `<BaseFooButton />`。
若不想包含特定路徑在元件名稱中,則需要在 `nuxt.config.js` 中指明:
```javascript=
components: {
dirs: [
'~/components',
'~/components/base'
]
}
```
則原本的 `<BaseFooButton />` 就可以改成 `<FooButton />` 。
### assets & static: 靜態檔案
* `assets` : 會經過 `Nuxt` 編譯壓縮打包。例如:第三方套件的 css 檔。
* `static` : 不會經過 `Nuxt` 編譯壓縮打包。例如:json 檔。
### middleware & store & plugins
* `middleware` : 進入頁面前需要處理的事。例如:驗證。
* `store` : 放 `Vuex` 的資料。
* `plugins`: 自定義 `Nuxt` 的套件。從 `nuxt.config.js` 中做引入,供全局環境使用。
## Nuxt 生命週期
[Nuxt Lifecycle - NuxtJS](https://nuxtjs.org/docs/2.x/concepts/nuxt-lifecycle)
![](https://i.imgur.com/8GYqHth.png)
### `asyncData` v.s. `fetch`
* [Data Fetching - NuxtJS](https://nuxtjs.org/docs/2.x/features/data-fetching)
* 適用於非同步資料取得。
### `asyncData`
頁面被渲染以前,在 server 端執行的生命週期函式。
例如:非同步處理,做 SEO。
:::warning
1. `asyncData` 只會執行一次!
2. `asyncData` 只能在 `pages` 資料夾底下的 `.vue` 中使用!
3. `asyncData` return 的值會覆蓋掉 `data` 中取相同名稱的值。可以直接 return 值到 template。
5. 無法使用與 window, document 等瀏覽器相關的 api。例如:alert
6. 沒有 `this` ! (`this` 是指 Vue 實體)
:::
### `fetch`
:warning: 只能在 Nuxt 2.12 之後的版本使用。
* [Data Fetching - NuxtJS](https://nuxtjs.org/docs/2.x/features/data-fetching)
* [The Fetch Hook - NuxtJS](https://nuxtjs.org/docs/2.x/components-glossary/pages-fetch)
頁面被渲染以前,在 server 端及 client 端執行的生命週期函式。
* server 端:當 route 被渲染時,初始頁面被渲染前。
* client 端:當進行導向時(navigating)。
與 `asyncData` 不同的地方:
1. 可以在任何 .vue component 中使用 (不限於 `pages` 資料夾)。
2. 可以取用 `this`。因為在 `created` 之後,Vue 的實體就會被 new 出來。
3. 不行 return 值到 template。
![](https://i.imgur.com/vhIEWUS.png)
#### 快捷 shortcuts
提供 `$fetchState` 在非同步資料取得時使用
* `$fetchState.pending`: `Boolean`。讓你在 client 端判斷 `fetch` 執行完沒。可以在 pending 為 true 時,放 loading 的顯示。
```htmlmixed=
<div v-if="$fetchState.pending">Loading...</div>
```
* `$fetchState.error`: `null` or `Error ({Obj})`。判斷 `fetch` 是否執行出錯,可將 Error 呈現出來。
```htmlmixed=
<div v-if="$fetchState.error">Error {{ $fetchState.error }}</div>
```
* `$fetchState.timestamp`: `Integer`。最後一次執行 `fetch` 的時間。適合與 `keep-alive` 的暫存功能搭配使用。
#### `keep-alive`
`keep-alive` 可以保存已造訪過頁面的 `fetch` 執行結果。
在 `<Nuxt />` 或 `<NuxtChild />` 中加入 `keep-alive` 指令。
```htmlmixed=
<template>
<Nuxt keep-alive />
</template>
```
可以使用 `keep-alive-props` 來指定要傳入的 props
```htmlmixed=
<template>
<Nuxt keep-alive :keep-alive-props="{ max: 10 }" />
</template>
```
#### `activated` 生命週期
只有在距上次呼叫 `fetch` 後的 30 秒,才會再次執行 `fetch`。
使用 `this.$fetch()` 可手動執行 `fetch` 生命週期。
```htmlmixed=
<template> ... </template>
<script>
export default {
data() {
return {
posts: []
}
},
activated() {
// Call fetch again if last fetch more than 30 sec ago
if (this.$fetchState.timestamp <= Date.now() - 30000) {
this.$fetch()
}
},
async fetch() {
this.posts = await fetch('https://api.nuxtjs.dev/posts').then(res =>
res.json()
)
}
}
</script>
```
#### 參數 Options
* `fetchOnServer` : `Boolean` or `Function` (預設為 true)
若 `fetchOnServer`: false,則 `fetch` 只會在 client 端執行。
```javascript=
export default {
data() {
return {
posts: []
}
},
async fetch() {
this.posts = await fetch('https://api.nuxtjs.dev/posts').then(res =>
res.json()
)
},
// call fetch only on client-side
fetchOnServer: false
}
```
#### 監聽 `$route.query`
當 router 的 query string 改變時,是不會執行 `fetch` 生命週期。可以使用 `watch` 進行監聽。
```javascript=
export default {
watch: {
'$route.query': '$fetch'
},
async fetch() {
// Called also on query changes
}
}
```
### `beforeCreate` & `created`
`beforeCreate` 與 `created` 在 server 端與 client 端都會被執行。
```javascript=
export default {
asyncData () {
console.log('asyncData')
},
beforeCreate () {
console.log('beforeCreate')
},
created () {
console.log('created')
},
fetch () {
console.log('fetch')
},
beforeMount () {
console.log('beforeMount')
},
mounted () {
console.log('mounted')
}
}
```
![](https://i.imgur.com/U9L9iW1.png)
## Meta tags & SEO
[Meta Tags and SEO - NuxtJS](https://nuxtjs.org/docs/2.x/features/meta-tags-seo)