# 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)