利用Nuxt.js建立服務端渲染的Vue.js應用程式
Nuxt.js 是一個 Vue 同構應用程式開發框架。本文將介紹為什麼要選擇 Nuxt、如何建立一個 Nuxt 專案、Nuxt 專案結構、Nuxt 的強化元件、使用服務端渲染時的考量、Nuxt 在各種環境的部署以及涉及的一些基本概念。希望能夠鼓勵你嘗試 Nuxt 來進行快速開發,並利用 Nuxt 提供的強大功能創造出互動豐富的卓越使用者體驗。
像 Vue 之類的 JavaScript 框架(或庫)可以在瀏覽你的網站時提供奇妙的使用者體驗。大多數框架都提供了一種動態改變頁面內容,而無需每次都向服務端傳送一個請求。
然而,這種方案有一個問題。剛開始載入你的網站時,瀏覽器不會接收到完整的顯示頁面。相反地,瀏覽器收到一堆用來構建頁面的程式碼片段(HTML、CSS 和其它檔案)和如何將這些程式碼片段組裝起來的指令(一個 JavaScript 框架或庫)。在瀏覽器真正有可以顯示的東西之前,需要先花費一段時間來將這些資訊拼裝起來,這有點像收到一堆書和一個扁平包裝的書櫃。
解決這個問題的方案很巧妙:在服務端放一個能夠構建出隨時顯示的頁面的框架(或庫)版本,然後將這個完整頁面傳送給瀏覽器,附帶進一步改變的能力和動態頁面內容(框架 / 庫),就像傳送一個現有的書櫃和一些書,當然,你仍然需要將這些書放到書櫃中,但是你已經得到了一些立即可用的東西。
除了這個可能不太巧妙的比喻,Nuxt 還有許多其它優勢。舉個例子,一個很少改變的頁面,比如一個 關於我們 頁面,並不需要每次在使用者訪問它的時候重新建立。因此,伺服器可以只建立它一次,然後將它快取或儲存在某個地方以備將來使用。這種速度提升可能看起來很小,但是在一個響應時間以毫秒(或更少)來衡量的環境下,每一點小的提升都很重要。
如果你想了解關於 Vue 環境下的服務端渲染(Server-Side Rendering,SSR)優勢的更多資訊,可以檢視 Vue 自己的關於服務端渲染的文章。雖然有許多實現服務端渲染的可選方案,但是 Nuxt 是最流行的方案,也是 被 Vue 團隊所推薦的方案。
為什麼選 Nuxt.js
Next 是一個針對流行的 React 庫的服務端渲染實現。在看到了這種設計的優勢之後,以它為基礎,設計了一個針對 Vue 的類似實現,即 Nuxt。熟悉 React+Next 組合的人,會在設計和應用程式佈局方面發現許多相似之處。然而,Nuxt 提供了一些 Vue 特有的功能來針對 Vue 建立一個功能強大而靈活的服務端渲染解決方案。
Nuxt 在 2018 年 1 月份 更新到 1.0 生產環境就緒版本,並且成為活躍的受到廣泛支援的社群的一部分。其中最重要的一點是,使用 Nuxt 構建一個專案和構建其它 Vue 專案沒有太多不同。事實上,它提供了一堆特性,使你能夠以更少的時間建立結構良好的程式碼庫。
另外值得一提的是,Nuxt 不一定非要用於服務端渲染。它被推動作為一個 Vue.js 同構應用程式開發庫,包含了一個使用相同程式碼建立靜態生成的 Vue 應用程式的命令(nuxt generate
)。因此,如果你對於深入研究服務端渲染顧慮重重,不要驚慌。你仍然可以利用 Nuxt 的特性建立一個靜態站點。
我們可以建立一個簡單的專案來深入理解 Nuxt。這個專案的最終原始碼 託管在 GitHub 上,你可以隨便檢視,或者,你可以檢視託管在 Netlify 上的使用nuxt generate
建立的 實時版本。
建立一個 Nuxt 專案
首先,讓我們使用一個名為vue-cli
的 Vue 專案生成器快速建立一個示例專案:
# install vue-cli globally
npm install -g vue-cli
# create a project using a nuxt template
vue init nuxt-community/starter-template my-nuxt-project
經過幾個選項之後,就會在my-nuxt-project
目錄或者你指定的其它目錄建立一個專案。然後我們只需要安裝依賴,執行伺服器:
cd my-nuxt-project
npm install # Or yarn
npm run dev
這樣做之後,確保專案正在執行,然後開啟瀏覽器,訪問localhost:3000
。這跟建立一個 Vue Webpack 專案沒有太多不同。然而,當我們檢視應用程式的實際結構時,並沒有太多檔案,特別是和類似 Vue Webpack 模版之類的檔案比較起來的話。
package.json
也顯示我們只有一個依賴——Nuxt 它本身。這是因為 Nuxt 的每個版本都是針對特定版本的 Vue、Vue-router 和 Vuex 進行定製並將它們打包在一起的。
在專案的根目錄還有一個nuxt.config.js
檔案。這使得你能夠自定義許多 Nuxt 提供的功能。預設情況下,它會為你設定頭部標籤、載入條顏色以及 ESLint 規則。如果你著急想看看你能夠進行哪些配置,這裡有一個文件連結;我們將在本文介紹到其中的一些配置項。
那麼,這些目錄有什麼特別的呢?
專案結構
如果你瀏覽一遍創建出來的目錄,它們都會有一個附帶的 Readme 檔案。這個 Readme 檔案通常以一段介紹這個目錄是幹什麼的簡短總結和一個文件連結開頭。
使用 Nuxt 的一個好處是:一個預設的應用程式結構。任何 優秀的前端開發者 都會類似這樣來安排應用程式結構,但是關於應用程式結構存在許多不同的想法,而團隊協作時,不可避免地會投入一些時間來討論或選擇應用程式結構。Nuxt 給你提供了一個,(通常就可以避免再花費這些時間)。
Nuxt 會尋找特定的目錄,然後基於它找到的目錄來為你構建你的應用程式。讓我們逐個看看這些目錄。
Pages
這是唯一的 必需 目錄。這個目錄中的任何 Vue 元件都會基於它們的檔名稱和目錄結構自動新增到vue-router
中。這點真是超級方便。通常,我會有一個獨立的 Pages 目錄,然後必需手動將其中的每個元件註冊到另一個路由檔案中。對於比較大型的專案,這個路由檔案會變得複雜,可能需要進行拆分來維持可讀性。相反地,Nuxt 會為你處理所有這些邏輯。
為了演示,我們可以在 Pages 目錄建立一個名為about.vue
的 Vue 元件。讓我們新增一個簡單的模板:
<template>
<h1>About Page</h1>
</template>
當你儲存的時候,Nuxt 會為你重新生成對應的路由。可以看到,因為我們將元件命名為about.vue
,如果你導航到/about
,你應該就會看到那個元件。很簡單。
有一個檔名很特別。將一個檔案命名為index.vue
會為那個目錄建立一個根路由。當專案生成的時候,在 Pages 目錄已經有一個index.vue
元件,它關聯到你站點的主頁或著陸頁(landing page)。(在這個開發示例中,指的是localhost:3000
。)
更深的路由會怎麼樣呢?Pages 目錄的子目錄可以幫你構建自己的路由結構。因此,如果我們想要一個 View Product 頁面,我們可以將 Pages 目錄構建成如下的結構:
/pages
--| /products
----| index.vue
----| view.vue
現在,如果我們導航到/products/view
,我們會看到 products 目錄中的view.vue
元件。如果導航到products
,我們會看到 products 目錄中的index.vue
目錄。
你可能會問,我們為什麼不在 Pages 目錄建立一個products.vue
元件,就像我們建立/about
頁面那樣。你可能會認為結果是相同的,但是這兩種結構有一點區別。讓我們通過新增另一個新頁面來演示這個區別:
假如說,我們想要為每個員工建立一個單獨的 About 頁面。例如,為我建立一個 About 頁面。這個頁面應該位於/about/ben-jones
。一開始,我們可能像下面這樣構建 Pages 目錄結構:
/pages
--| about.vue
--| /about
----| ben-jones.vue
當我們訪問/about/ben-jones
,會得到about.vue
元件,和訪問/about
時一樣。這裡發生了什麼?
有意思的是,Nuxt 在這裡構建了一個巢狀的路由。這種結構表示,你想要一個永久的/about
路由,並且那個路由中的任何東西都應該巢狀在它自己的檢視區域。在 vue-router 中,這會通過在about.vue
元件中指定一個<router-view />
元件來表示。在 Nuxt 中,概念相同,但我們用<nuxt />
而不是<router-view />
。讓我們更新about.vue
元件來實現巢狀路由:
<template>
<div>
<h1>About Page</h1>
<nuxt />
</div>
</template>
現在,當我們導航到/about
,我們得到了之前的about.vue
元件,只有一個標題。然而,當我們導航到/about/ben-jones
時,我們會看到那個標題和渲染在<nuxt/>
佔位符所在位置的ben-jones.vue
元件。
這並不是我們最初想要的,但是這個想法(存在一個人物列表,每個人物有一個 About 頁面,當點選的時候,將對應人物資訊填充到頁面的某個區域)是一個有意思的概念,我們目前可以先把它放到一邊。如果你確實想要其它選項,那麼我們要做的就是重新構建我們的目錄結構。我們只需要將about.vue
元件移動到/about
目錄,然後將它重新命名為index.vue
,因此,得到的目錄結構會是:
/pages
--| /about
----| index.vue
----| ben-jones.vue
最終,假如說,我們想要使用路由引數來獲取某個具體的產品。例如,我想要通過導航到/products/edit/64
來編輯某個產品,而 64 是product_id
。我們可以實現如下:
/pages
--| /products
----| /edit
------| _product_id.vue
注意_product_id.vue
元件開頭的下劃線,這表示一個路由引數,可以稍後在$route.params
物件或者 Nuxt 的 Context 中的params
物件(稍後再作更多介紹)上訪問這個引數。注意,這個引數的鍵是元件名稱去掉開始的下劃線,在這個例子中就是product_id
,因此使它們在整個專案中保持唯一。因此,在_product_id.vue
中,我們會有如下程式碼:
<template>
<h1>Editing Product {{ $route.params.product_id }}</h1>
</template>
你可以想象更復雜的佈局,那些佈局使用 vue-router 配置會非常痛苦。例如,我們可以將上述內容都組合到一個如下路由中:
/pages
--| /categories
----| /_category_id
------| products.vue
------| /products
--------| _product_id.vue
不難推理出/categories/2/products/3
會顯示什麼。我們會得到products.vue
元件和一個巢狀的_product_id.vue
元件,附帶 2 個路由引數:category_id
和product_id
。進行推理時,這比同等的路由配置要簡單得多。
當我們談到這個話題時,我想要在路由配置中做的一件事是設定路由攔截。由於 Nuxt 為我們構建路由,因此我們可以通過元件本身的beforeRouterEnter
設定路由攔截。如果你想要驗證路由引數,Nuxt 提供了一個名為validate
的元件方法。因此,如果你想要在渲染元件之前檢查product_id
是否是一個數字,你需要在_product_id.vue
的 script 標籤中增加如下程式碼:
export default {
validate ({ params }) {
// Must be a number
return /^\d+$/.test(params.product_id)
}
}
現在,導航到/categories/2/products/someproduct
會返回一個 404 頁面,因為someproduct
不是一個正確的數字。
這就是 Pages 目錄的內容。瞭解如何在這個目錄中恰當構建你的路由結構是很重要的,因此,一開始花一些時間來學習它是很重要的。如果你正在尋找一份概要,參考 路由相關文件 通常會有所幫助。
如果你擔心路由不受控制,那大可不必。這個預設設定對於各種各樣的專案來說都執行得很好,只要它們結構良好。然而,有一些情況下,你可能需要增加一些 Nuxt 自動為你生成的路由之外的路由,或者需要重構它們。Nuxt 提供了一種在配置中自定義路由例項的方法,使你能夠新增路由並自定義生成的路由。你還可以編輯路由例項的核心功能,包括 Nuxt 增加的額外選項。因此,如果你確實遇到了一個極端例子,你還可以靈活地找到合適的解決方案。
Store
和 Pages 目錄有點類似,Nuxt 可以基於 Store 目錄結構構建你的 Vuex store。如果你不需要 store,只需要刪除這個目錄就可以了。有兩種模式的 store:經典模式和模組模式。
經典模式需要在 Store 目錄有一個index.js
檔案。在這個檔案中,你需要匯出一個返回了一個 Vuex 例項的函式:
import Vuex from 'vuex'
const createStore = () => {
return new Vuex.Store({
state: ...,
mutations: ...,
actions: ...
})
}
export default createStore
這使你能夠建立任何你想要的 store,這與在一個普通 Vue 專案中使用 Vuex 非常像。
模組模式也需要你在 Store 目錄建立一個index.js
檔案。然而,這個檔案只需要為你的 Vuex store 匯出根 state/mutations/actions。下面的例子指定了一個空的根 state:
export const state = () => ({})
然後,store 目錄中的每個檔案都會以它自己的名稱空間或模組增加到 store 中。例如,讓我們建立某個 store 來儲存當前產品。如果我們在 store 目錄建立一個名為product.js
的檔案,那麼可以在$store.product
訪問 store 下對應名稱空間的部分。下面是一個簡單的例子:
export const state = () => ({
_id: 0,
title: 'Unknown',
price: 0
})
export const actions = {
load ({ commit }) {
setTimeout(
commit,
1000,
'update',
{ _id: 1, title: 'Product', price: 99.99 }
)
}
}
export const mutations = {
update (state, product) {
Object.assign(state, product)
}
}
在 load action 中的setTimeout
模擬了某種型別的 API 呼叫,會在響應中更改 store;在這個例子中,它需要 1 秒鐘(才會響應)。現在,讓我們在products/view
頁面中使用它:
<template>
<div>
<h1>View Product {{ product._id }}</h1>
<p>{{ product.title }}</p>
<p>Price: {{ product.price }}</p>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
created () {
this.$store.dispatch('product/load')
},
computed: {
...mapState(['product'])
}
}
</script>
需要注意:這裡,我們是在元件建立時,呼叫了我們假的 API。你可以看到,我們正在排程的product/load
action 是在 Product 名稱空間下。這樣,我們正在處理 store 的哪個部分就顯得非常清楚。然後,通過將 state 對映到一個本地的 computed 屬性,我們可以在模版中輕鬆使用它。
有一個問題:在 API 執行的時候,我們可以持續看到原始狀態 1 秒鐘。稍後,我們會使用 Nuxt 提供的一種解決方案來修復這個問題(稱為fetch
)。
再次強調,我們從來不需要執行npm install vuex
,因為它已經包含在 Nuxt 包中了。當你在 store 目錄增加一個index.js
檔案時,所有這些方法都會向你自動開啟。
其中主要的 2 個目錄已經解釋了;剩下的目錄就簡單多了。
Components
Components 目錄用來包含你的可複用元件,例如導航條、圖片夾、分頁、資料表格等。由於 Pages 目錄中的元件會被轉換為路由,因此你需要在其它地方儲存這些元件類。可以通過引用這些元件而在頁面或其它元件中訪問它們:
import ComponentName from ~/components/ComponentName.vue
Assets
這個目錄包含未編譯的資源,與 Webpack 如何載入和處理檔案有更多關係,而與 Nuxt 如何工作沒有太多關係。如果你對此感興趣,可以閱讀 Readme 中的指南。
Static
這個目錄包含一些對映到你的站點的根目錄的靜態檔案。例如,將一個名為logo.png
的圖片放到這個目錄,就可以在/logo.png
訪問它。這很適合一些諸如 robots.txt、favicon.ico 和其它你需要可被訪問的元檔案。
Layouts
通常,在一個 Vue 專案中,你有某種根元件,通常名為App.vue
。在這裡,你可以設定你的(通常是靜態的)應用程式的佈局。這個佈局可能會包含一個導航欄、頁尾和一個用於 vue-router 的內容區。default
佈局就是這樣的,在 layouts 目錄中提供了這種預設佈局。最初,它只有一個 div 和一個<nuxt />
元件(等同於<router-view />
),但它可以任你調整。例如,我向示例專案中增加了一個簡單的導航欄,用於導航各種演示頁面。
你可能會想讓你的 App 的特定區域有一種不同的佈局。也許,你有某種看起來不太一樣的內容管理系統(CMS)或者管理看板。為了解決這個問題,可以在 Layouts 目錄建立一個新的佈局。舉個例子,讓我們建立一個admin-layout.vue
佈局,只有一個額外的 header 標籤而沒有導航條:
<template>
<div>
<h1>Admin Layout</h1>
<nuxt />
</div>
</template>
然後,我們可以在 Pages 目錄建立一個admin.vue
頁面,然後使用 Nuxt 提供的一個名為layout
的屬性來指定我們想要為那個元件使用的佈局名稱(一個字串)。
<template>
<h1>Admin Page</h1>
</template>
<script>
export default {
layout: 'admin-layout'
}
</script>
這就是所有程式碼了。Page 元件將使用預設佈局除非指定了佈局,但是當你導航到/admin
,它現在使用admin-layout.vue
佈局。當然,如果你想的話,這個佈局可以在幾個管理螢幕之間共享。需要牢記的是,佈局必須包含一個<nuxt />
元素。
關於佈局,還有最後一點需要注意。你可能已經在實驗時注意到了,如果你輸入一個不正確的 URL,會顯示一個錯誤頁面。事實上,這個錯誤頁面是另外一種佈局。Nuxt 有它自己的錯誤頁面佈局(原始碼連結),但是如果你想要編輯它,只需要建立一個error.vue
佈局,然後 Nuxt 就會使用這個佈局代替預設的佈局。這裡需要提醒的是,錯誤頁面佈局一定不要有一個<nuxt />
元素。你還可以訪問元件上的一個error
物件,它包括一些要顯示的基本資訊。(這會列印在執行 Nuxt 的終端中,如果你想要檢查這點的話。)
Middleware
中介軟體(Middleware)是一些可以在渲染一個頁面或佈局之前被執行的函式。有各種各樣的原因你想要這樣做。路由攔截是一種流行用途,其中你可以通過檢查 Vuex store 來校驗登入是否合法,或者校驗一些引數(而不是在元件本身上使用validate
方法)。我最近從事的一個專案使用中介軟體來基於路由和引數生成動態麵包屑導航(breadcrumbs)。
這些函式可以是非同步的;需要小心,因為直到中介軟體處理完畢之前不會向用戶顯示任何東西。這些函式還可以訪問到 Nuxt 的 Context,我稍後會解釋這一點。
Plugins
這個目錄使你能夠在應用程式被建立之前註冊 Vue 外掛。這使得外掛可以在你的應用程式上的 Vue 例項中共享,並可以在任何一個元件中使用。
大部分主流外掛都有 Nuxt 版本,通過遵循它們的文件,可以輕鬆將它們註冊到 Vue 例項。然而,你也可以開發一個外掛或者對一個現有外掛進行適配。我從 Nuxt 文件中借用的一個例子展示瞭如何對vue-notifications
進行適配。首先,我們需要安裝這個包:
npm install vue-notifications --save
然後,在 plugins 目錄建立一個名為vue-notifications
的檔案,包含如下內容:
import Vue from 'vue'
import VueNotifications from 'vue-notifications'
Vue.use(VueNotifications)
這與你向普通的 Vue 環境註冊一個外掛非常相似。然後在你專案的根目錄編輯nuxt.config.js
檔案,並將下面的內容新增到 module.exports 物件中:
plugins: ['~/plugins/vue-notifications']
這就行了。現在,你可以在你的應用程式中使用vue-notifications
。在示例
專案的/plugin
有一個例子。
這樣就逐個介紹完了 Nuxt 目錄結構。這可能看起來有很多東西要學,但是如果你正在開發一個 Vue 應用程式,你其實已經在設定相同型別的邏輯。Nuxt 只是幫助簡化了設定,幫你將精力集中在應用程式構建上。
其實,Nuxt 做的不僅僅是輔助開發。它通過提供的額外功能,強化了你的元件。
Nuxt 的強化元件
當我剛開始研究 Nuxt 的時候,我一直在看 Page 元件是如何被強化的。強化元件雖然聽起來很棒,但是它究竟意味著什麼以及它能帶來什麼好處並不能立即顯現出來。
強化元件意味著,Nuxt 給所有的 Page 元件綁定了額外的方法,Nuxt 可以使用這些方法來提供額外的功能。事實上,我們先前使用validate
方法來檢查引數和重定向非法使用者時,已經看過其中一個強化元件了。
在一個 Nuxt 專案中被使用的 2 個主要方法是asyncData
和fetch
方法。它們在概念上非常相似,在元件生成之前非同步執行,而且它們可以用來獲取元件和 sotre 的資料。它們還能夠使頁面在傳送到客戶端之前在服務端完全渲染,即使我們必須等待一些資料庫或 API 呼叫。
asyncData
和fetch
之間有什麼不同?
-
asyncData
是用來獲取 Page 元件的資料。當你返回一個物件,它會在渲染之前,與輸出資料進行合併。 -
fetch
是用來獲取 Vuex Store。如果你返回一個 promise,Nuxt 會在渲染之前一直等待 promise 處理完畢。
那麼,讓我們好好利用這些功能。還記得在/products/view
頁面,我們有一個問題,即在我們呼叫假 API 時,store 的最初狀態被會短暫顯示?修復這個問題的一個方法是,在元件或 Store 上儲存一個布林值,例如loading = true
,然後在呼叫 API 時顯示一個載入中元件。API 呼叫完成之後,我們設定loading = false
並顯示資料。
作為替代,我們可以在渲染之前使用fetch
獲取 Store。在一個名為/products/view-async
的新頁面,我們將created
方法改為fetch
;這樣應該可行,對吧?
export default {
fetch () {
// Unfortunately the below line throws an error
// because 'this.$store' is undefined...
this.$store.dispatch('product/load')
},
computed: {...}
}
這裡有個問題:這些“強化”方法在元件被建立之前執行,因此不會指向對應的元件,而且不能訪問元件上的任何東西。那麼,我們這裡如何訪問 Store?
The Context API
當然是有方案的。在所有 Nuxt 方法中,會被提供一個包含一個 Context 物件的引數(通常是第一個引數),這個物件非常有用。這個物件中,就是你就會跨應用程式引用到的任何東西。這意味著,我們不需要等待 Vue 預先在對應元件上建立那些引用。
我強烈推薦檢視 Context 文件 來看看有什麼可用的內容。比較方便的有app
,你可以通過它訪問你的所有外掛;redirect
,可以用來改變路由;error
用來顯示錯誤頁面;以及一些一目瞭然的屬性,例如route
、query
、store
等。
因此,為了訪問 Store,我們可以解構 Context 並從中提取 Store。我們還需要確保返回了一個 promise,這樣 Nuxt 可以在渲染元件之前等待這個 promise 解決,因此我們還需要對我們的 Store action 做一些小的調整。
// Component
export default {
fetch ({ store }) {
return store.dispatch('product/load')
},
computed: {...}
}
// Store Action
load ({ commit }) {
return new Promise(resolve => {
setTimeout(() => {
commit('update', { _id: 1, title: 'Product', price: 99.99 })
resolve()
}, 1000)
})
}
你可以根據自己的編碼風格,使用 async/await 或者其它方法,但是概念是相同的——我們讓 Nuxt 在嘗試渲染元件之前確保 API 已經呼叫結束並且 Store 已經用返回結果更新了。如果你導航到/products/view-async
,不會再閃現處於原始狀態的產品內容。
你可以想象,即使沒有服務端渲染,這在任何 Vue 應用程式中會多麼有用。Context 還可以在所有中介軟體以及其它 Nuxt 方法,例如nuxtServerInit
中訪問。nuxtServerInit
是一個特殊的 store action,在 Store 初始化之前執行(下個章節中有一個關於它的例子)。
使用服務端渲染時的考量
我確信,許多開始使用 Nuxt 之類技術的人(包括我自己),在將它當作任何其它 Vue 專案的時候,最後都會碰壁——我們通常認為可以起效的事情在 Nuxt 中看起來是不可能的。隨著更多的這些警告被記錄,這會變得更容易克服,但是在開始除錯時主要需要考慮的是,客戶端和服務端是兩個獨立的實體。
當你剛開始訪問一個頁面的時候,會向 Nuxt 傳送一個請求,服務端會盡可能多地構建那個頁面和應用程式的剩餘部分,然後將它傳送給你。然後,客戶端負責繼續導航並按需載入對應的部分。
我們想要服務端在開始的時候儘可能多地做事情,但是有時候,服務端並沒有它所需要的資訊,從而導致相應的工作要在客戶端完成。更糟糕的是,當客戶端展示的最終內容與服務端預期的內容不同時,會讓客戶端重頭開始重新構建。這是一個很大的跡象表明應用程式的邏輯在某些地方出了問題。謝天謝地,(在開發環境),如果應用程式的邏輯發生了錯誤,在你的瀏覽器控制檯會生成一個錯誤。
讓我們以如何解決會話管理這個常見問題為例。想象你有一個 Vue 應用程式,你可以登入一個賬戶,然後使用一個 token(例如 JWT)儲存你的會話,你可能決定將這個 token 保持在localStorage
。當你一開始訪問站點的時候,你想要通過一個 API 來驗證那個 token,如果 token 合法,那個 API 會返回一些基本的使用者資訊並將這些資訊放到 Store 中。
讀完 Nuxt 的文件之後,你可以看到,有一個方便的方法nuxtServerInit
可以使你在剛開始載入的時候獲取 Store 資料。這聽起來很完美!因此,你在 Store 中建立了你的使用者模組,並在 Store 目錄的index.js
檔案中增加適當的 action:
export const actions = {
nuxtServerInit ({ dispatch }) {
// localStorage should work, right?
const token = localStorage.getItem('token')
if (token) return dispatch('user/load', token)
}
}
當你重新整理頁面,你會得到一個錯誤——localStorage is not defined
(localStorage 未被定義)。想一想這個錯誤是在哪裡發生的,一切就清楚了。這個方法執行在服務端,而服務端不清楚客戶端上的localStorage
中儲存了什麼;事實上,服務端甚至不知道“localStorage“是什麼!因此,上述方法是行不通的。
那麼,解決方案是什麼呢?事實上,還是有一些解決方案的。你可以讓客戶端來初始化 Store,但會喪失服務端渲染的好處,因為客戶端最終做了所有事情。你可以在服務端建立會話,然後用它來驗證使用者,但那又是另一個層次的事情了。最類似於localStorage
的替代方法是使用 cookies。
Nuxt 可以訪問 cookies,因為它們會隨著請求一起從客戶端傳送到服務端。和其它 Nuxt 方法一樣,nuxtServerInit
可以訪問 Context,這次是作為第二個引數,因為第一個引數是保留給 store 的。在 Context 上,我們可以訪問req
物件,它儲存了所有的請求頭和客戶端請求的其它資訊。(如果你用過 Node.js,就會對這些感到特別熟悉。)
因此,在一個 cookie 中儲存 token(本例中,稱為“token”)之後,讓我們在服務端訪問它。
import Cookie from 'cookie'
export const actions = {
nuxtServerInit ({ dispatch }, { req }) {
const cookies = Cookie.parse(req.headers.cookie || '')
const token = cookies['token'] || ''
if (token) return dispatch('user/load', token)
}
}
這是一個簡單的解決方案,但可能不是能夠讓人立即明白。學著去思考,特定的活動發生在什麼地方(客戶端、服務端或者同時這兩端),以及它們訪問的哪些內容雖然會需要一些時間但其好處是值得的。
部 署
部署 Nuxt 非常簡單。使用同樣的程式碼,你可以建立一個服務端渲染應用程式、單頁應用程式或者靜態頁面。
服務端渲染應用程式(SSR App)
這可能就是你使用 Nuxt 的目的。部署的基本概念是,在你選擇的任何平臺執行build
程序並設定一些配置。我將使用 文件 中的 Heroku 例子:
首先,在package.json
中設定好 Heroku 的指令碼:
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"heroku-postbuild": "npm run build"
}
然後使用heroku-cli
安裝好 Heroku 環境(安裝指令如下:
# set Heroku variables
heroku config:set NPM_CONFIG_PRODUCTION=false
heroku config:set HOST=0.0.0.0
heroku config:set NODE_ENV=production
# deploy
git push heroku master
這就可以了。現在,你的服務端渲染 Vue 應用程式已經上線了,全世界都看得到。其它平臺的安裝步驟不同,但是過程類似。目前其官方部署方法列舉如下:
-
Dokku:Digital Ocean :https://nuxtjs.org/faq/dokku-deployment
單頁應用程式(SPA)
如果你想要利用 Nuxt 提供的其它特性,但是避免服務端渲染頁面,那麼你可以將它作為一個 SPA 部署。
首先,最好關閉服務端渲染來測試你的應用程式,因為預設地,npm run dev
執行時會開啟服務端渲染。可以編輯nuxt.config.js
檔案並增加如下配置項來改變這點:
mode: 'spa',
現在,當你執行npm run dev
,服務端渲染會被關閉,而應用程式會作為一個單頁應用程式執行來供你測試。這個設定也會確保未來的構建都不會包含服務端渲染。
如果看起來一切正常,那麼其部署過程和一個服務端渲染應用程式其實是相同的。只要記住,你需要先設定mode: 'spa'
來讓構建程序知道你想要一個單頁應用程式。
靜態頁面
如果你一點兒也不想和伺服器打交道,而只想要生成一些頁面,用於靜態託管服務,例如 Surge 或 Netlify,那麼你也可以用 Nuxt。只需要牢記,沒有伺服器,你就不能在 Context 中訪問req
和res
,因此,如果你的程式碼依賴這些,最好確保對它進行適配。例如,在生成示例專案時,nuxtServerInit
函式因為試圖從請求頭的 cookies 中獲取 token 而丟擲了一個錯誤。在這個專案中,這沒什麼影響,因為那些資料在任何地方都不會被用到,但是在一個真實的應用程式中,就會需要有一種備選方法來獲取那些資料。
一旦就緒,部署就很簡單了。你可能首先需要改變的是,增加一個配置項,從而使nuxt generate
命令也會建立一個反饋檔案。這個檔案會提示託管服務讓 Nuxt 處理路由而不是託管服務處理路由,丟擲一個 404 錯誤。為了實現這點,要向nuxt.config.js
中增加如下一行程式碼:
generate: { fallback: true },
這裡有一個使用 Netlify 的例子,目前不在 Nuxt 文件中。只要牢記,如果你是第一次使用netlify-cli
,你會被提示進行身份驗證:
# install netlify-cli globally
npm install netlify-cli -g
# generate the application (outputs to dist/ folder)
npm run generate
# deploy
netlify deploy dist
就是這麼簡單!正如本文開頭說的,這裡 有一個這個專案的版本。下述服務也有各自的官方部署文件:
-
GitHub Pages:https://nuxtjs.org/faq/github-pages
瞭解更多
Nuxt 正迅速更新,這只是挑選出來的一小部分它提供的特性。我希望本文能夠鼓勵你去嘗試它,並看看它是否能夠幫助提升你的 Vue 應用程式的能力,使你能夠更快地開發並利用它的強大功能。
如果你想要獲取更多資訊,那麼沒有比 Nuxt 的官方連結更透徹的了:
-
Documentation:https://nuxtjs.org
-
Playground:https://glitch.com/edit/#!/nuxt-hello-world
-
本次給大家推薦一個免費的學習群,裡面概括移動應用網站開發,css,html,webpack,vue node angular以及面試資源等。
對web開發技術感興趣的同學,歡迎加入Q群:943129070,不管你是小白還是大牛我都歡迎,還有大牛整理的一套高效率學習路線和教程與您免費分享,同時每天更新視訊資料。
最後,祝大家早日學有所成,拿到滿意offer,快速升職加薪,走上人生巔峰。