在傳統許多 JS 套件都是和畫面渲染以及 HTML Dom 有關。然而 Nuxt 是 SSR,因此在使用套件 (或是自己開發) 的過程中,可能會遇到「window 和 document 沒有定義」這樣的錯誤訊息,接下來會介紹如何排除錯誤!
在 Nuxt 預設 component 裡面有一個 <no-ssr>
,此 component 將在 Nuxt 3.X 中捨棄,所以我們依照官方提示改用 <client-only>
。
<client-only>
顧名思義,在這個 component 底下的內容都會在 client 端渲染。通常會使用 Nuxt 多半都與 SSR 和 SEO 兩大需求脫離不了關係,所以如果要讓部分畫面由 client 端渲染,要稍微注意是否跟原始需求有衝突!
下面是一段很單純的 code,我們透過 process.client
與 process.server
來確定 <client-only>
底下內容的渲染時機。
<template>
<div>
<div>
A 區塊是由 {{renderSide()}} 渲染
</div>
<client-only>
B 區塊是由 {{renderSide()}} 渲染
</client-only>
</div>
</template>
<script>
export default {
name: 'index',
methods: {
renderSide() {
if (process.server) {
return 'Server 端';
} else if (process.client) {
return 'Client 端';
}
},
}
}
</script>
什麼!! 你說 A、B 區塊都是顯示 Client 端 嗎? 別緊張我們看看 console:
看吧! A 區塊確實是在 server 端渲染好的,同時 B 區塊也只在 client 端的時候才渲染。
就像開頭說的,有些套件可能會用到 window
或是 document
這類前端及 browser 的物件,因此在引用、使用套見的時候就會出錯。
Post
類別// 我知道 alert 訊息很詭異,但只是個範例 ><"
window.alert('Using Post ...');
export class ITPost {
constructor(title, content) {
this.title = title;
this.content = content;
}
setTitle(title) {
this.title = title;
}
setContent(content) {
this.content = content;
}
getTitle() {
return this.title;
}
getContent() {
return this.content;
}
}
ITPost
類別<template>
<div>
<div>{{itPost.getTitle()}}</div>
<div>{{itPost.getContent()}}</div>
</div>
</template>
<script>
import { ITPost } from '../helpers/ITPost';
export default {
name: 'Post',
props: {
post: {
required: true,
type: Object,
}
},
data() {
return {
itPost: undefined,
};
},
mounted() {
this.itPost = new ITPost(post.title, post.content);
}
}
</script>
<template>
<div>
<it-post :post="post"></it-post>
</div>
</template>
<script>
import ItPost from '../components/ItPost';
export default {
name: 'test',
components: {ItPost},
data() {
return {
post: {
title: 'Day 23. Vuex 和 Cookie 哪個好? 小朋友才做決定,我兩個都要',
content: '...'
}
};
}
}
</script>
一切看似沒問題,實際跑起來卻出現「window is not defined」
好的,那我們在 page component 加上 <client-only>
<template>
<div>
<client-only>
<it-post :post="post"></it-post>
</client-only>
</div>
</template>
結果還是錯RRR!
ItPost.vue
,我們將 import
改為 process.client
+ require()
就好了!<template>
<div>
<!-- 確保 itPost 是存在的 -->
<template v-if="itPost">
<div>{{itPost.getTitle()}}</div>
<div>{{itPost.getContent()}}</div>
</template>
</div>
</template>
<script>
export default {
name: 'Post',
props: {
post: {
required: true,
type: Object,
}
},
data() {
return {
itPost: undefined,
};
},
mounted() {
// 只有在 client 端才執行
if (process.client) {
const { ITPost } = require('../helpers/ITPost');
this.itPost = new ITPost(this.post.title, this.post.content);
}
}
}
</script>
我們從今天的範例可以看到,如果套件或是一些 Vue component 有用到 (更精確的說法是有執行到) 一些屬於前端和瀏覽器才有的相關物件與變數,我們可以透過 <client-only>
搭配 process.client
調整某些套件或是 component 要使用的時機!
Nuxt 的介紹到這邊告一個段落,明天開始要來寫寫幾個常用到的範例 (總不能連 Hello World 都沒有吧) ! 但是在結束之前,我們回顧 Nuxt lifecycle
不知道在最後看到這張圖有沒有感覺呢?
首先透過 nuxtServerInit
將 request 中的 cookie 重新塞回 Vuex state;接著先透過 route 對應上的所有 component 中的 middleware
過濾與驗證,接著可以透過 validate()
確認 route 上的變數是否合法,通過驗證之後,藉由 asyncData()
與 fetch()
串接 API 取得資料,必要時可以存入 Vuex state 當中,最後我們渲染畫面。這就是一個基礎的 page component 完整 lifecycle。
個人覺得從 Vue 轉到 Nuxt 沒有太多困擾 (當然也可能因為沒有寫到 scale 很大的案子),但是就學習曲線而言應該算是平易近人的,不知道大家覺得如何呢?
那今天就先這樣,我們明天見!