Nuxt
是解決SEO
的比較常用的解決方案,隨着Nuxt
也有很多坑,每當突破一個小技術點的時候,都有很大的成就感,在這段時間里着實讓我痛並快樂着。在這里根據個人學習情況,所踩過的坑做了一個匯總和總結。
Nuxt開發跨域
項目可以使用Nginx
來反向代理,將外來的請求(這里也注意下將Linux
的防火牆放行相應端口)轉發的內部Nuxt
默認的3000
端口上,最簡單的配置文件如下:
nuxtjs.config.js
{
modules: [
'@nuxtjs/axios',
'@nuxtjs/proxy'
],
proxy: [
[
'/api',
{
target: 'http://localhost:3001', // api主機
pathRewrite: { '^/api' : '/' }
}
]
]
}
@nuxtjs/proxy
需要手動單獨安裝。
Nuxt Store 使用
在Nuxt
中使用Vuex
跟傳統在Vue
中使用Vuex
還不太一樣,首先Nuxt
已經集成了Vuex
,不需要我們進行二次安裝,直接引用就好,在默認Nuxt
的框架模板下有一個Store
的文件夾,就是我們用來存放Vuex
的地方。
Nuxt
官方也提供了相關文檔,可以簡單的過一下,但是官方文檔我看來比較潦草。
根據官方文檔在store
文件下面創建兩個.js
文件,分別是index.js
和todo.js
。並在pages
文件夾下面創建index.vue
。
store - index.js
export const state = () => ({
counter: 0
})
export const mutations = {
increment (state) {
state.counter++
}
}
store - todo.js
export const state = () => ({
list: []
})
export const mutations = {
add (state, text) {
state.list.push({
text: text,
done: false
})
},
remove (state, { todo }) {
state.list.splice(state.list.indexOf(todo), 1)
},
toggle (state, todo) {
todo.done = !todo.done
}
}
pages - index.vue
<template>
<section class="container">
<div>
<h2 @click="$store.commit('increment')">{{counter}}</h2>
<ul>
<li v-for="(item,index) of list"
:key="index">{{item.text}}</li>
</ul>
</div>
</section>
</template>
<script>
import Logo from '~/components/Logo.vue'
import {mapState} from "vuex";
export default {
components: {
Logo
},
computed:{
...mapState(["counter"]),
...mapState("todos",{
list:state => state.list
})
},
created(){
for(let i =0;i<10;i++){
this.$store.commit("todos/add",i);
}
console.log(this.list)
}
}
</script>
在Nuxt
中可以直接使用this.$store
,並且是默認啟用命名空間的。再看一下computed
中的代碼,在使用mapState
的時候,counter
屬性是直接獲取出來的,然而todos
屬性則是通過命名空間才獲取到的。這又是怎么回事?
Nuxt
把store
中的index.js
文件中所有的state、mutations、actions、getters
都作為其公共屬性掛載到了,store
實例上,然而其他的文件則是使用的是命名空間,其對應的命名空間的名字就是其文件名。
運行項目的時候可以在.nuxt
文件夾內找到store.js
看下是怎么完成的。簡單的解釋一下代碼作用,以及做什么用的。
.nuxt - store.js
// 引入vue
import Vue from 'vue'
// 引入vuex
import Vuex from 'vuex'
// 作為中間件
Vue.use(Vuex)
// 保存console 函數
const log = console
// vuex的屬性
const VUEX_PROPERTIES = ['state', 'getters', 'actions', 'mutations']
// store屬性容器
let store = {}
// 沒有返回值的自執行函數
void (function updateModules() {
// 初始化根數據,也就是上面所說的index文件做為共有數據
store = normalizeRoot(require('@/store/index.js'), 'store/index.js')
// 如果store是函數,提示異常,停止執行
if (typeof store === 'function') {
// 警告:經典模式的商店是不贊成的,並將刪除在Nuxt 3。
return log.warn('Classic mode for store is deprecated and will be removed in Nuxt 3.')
}
// 執行存儲模塊
// store - 模塊化
store.modules = store.modules || {}
// 解決存儲模塊方法
// 引入todos.js 文件,即數據
// 'todos.js' 文件名
resolveStoreModules(require('@/store/todos.js'), 'todos.js')
// 如果環境支持熱重載
if (process.client && module.hot) {
// 無論何時更新Vuex模塊
module.hot.accept([
'@/store/index.js',
'@/store/todos.js',
], () => {
// 更新的根。模塊的最新定義。
updateModules()
// 在store中觸發熱更新。
window.$nuxt.$store.hotUpdate(store)
})
}
})()
// 創建store實例
// - 如果 store 是 function 則使用 store
// - 否則創建一個新的實例
export const createStore = store instanceof Function ? store : () => {
// 返回實例
return new Vuex.Store(Object.assign({
strict: (process.env.NODE_ENV !== 'production')
}, store))
}
// 解決存儲模塊方法
// moduleData - 導出數據
// filename - 文件名
function resolveStoreModules(moduleData, filename) {
// 獲取導出數據,為了解決es6 (export default)導出
moduleData = moduleData.default || moduleData
// 遠程store src +擴展(./foo/index.js -> foo/index)
const namespace = filename.replace(/\.(js|mjs|ts)$/, '')
// 空間名稱
const namespaces = namespace.split('/')
// 模塊名稱(state,getters等)
let moduleName = namespaces[namespaces.length - 1]
// 文件路徑
const filePath = `store/${filename}`
// 如果 moduleName === 'state'
// - 執行 normalizeState - 正常狀態
// - 執行 normalizeModule - 標准化模塊
moduleData = moduleName === 'state'
? normalizeState(moduleData, filePath)
: normalizeModule(moduleData, filePath)
// 如果是 (state,getters等)執行
if (VUEX_PROPERTIES.includes(moduleName)) {
// module名稱
const property = moduleName
// 存儲模塊 // 獲取存儲模塊
const storeModule = getStoreModule(store, namespaces, { isProperty: true })
// 合並屬性
mergeProperty(storeModule, moduleData, property)
// 取消后續代碼執行
return
}
// 特殊處理index.js
// 模塊名稱等於index
const isIndexModule = (moduleName === 'index')
// 如果等於
if (isIndexModule) {
// 名稱空間彈出最后一個
namespaces.pop()
// 獲取模塊名稱
moduleName = namespaces[namespaces.length - 1]
}
// 獲取存儲模塊
const storeModule = getStoreModule(store, namespaces)
// 遍歷 VUEX_PROPERTIES
for (const property of VUEX_PROPERTIES) {
// 合並屬性
// storeModule - 存儲模塊
// moduleData[property] - 存儲模塊中的某個屬性數據
// property - 模塊名稱
mergeProperty(storeModule, moduleData[property], property)
}
// 如果moduleData.namespaced === false
if (moduleData.namespaced === false) {
// 刪除命名空間
delete storeModule.namespaced
}
}
// 初始化根數據
// moduleData - 導出數據
// filePath - 文件路徑
function normalizeRoot(moduleData, filePath) {
// 獲取導出數據,為了解決es6 (export default)導出
moduleData = moduleData.default || moduleData
// 如果導入的數據中存在commit方法,則拋出異常
// - 應該導出一個返回Vuex實例的方法。
if (moduleData.commit) {
throw new Error(`[nuxt] ${filePath} should export a method that returns a Vuex instance.`)
}
// 如果 moduleData 不是函數,則使用空隊形進行合並處理
if (typeof moduleData !== 'function') {
// 避免鍵入錯誤:設置在覆蓋頂級鍵時只有getter的屬性
moduleData = Object.assign({}, moduleData)
}
// 對模塊化進行處理后返回
return normalizeModule(moduleData, filePath)
}
// 正常狀態
// - 模塊數據
// - 文件路徑
function normalizeState(moduleData, filePath) {
// 如果 moduleData 不是function
if (typeof moduleData !== 'function') {
// 警告提示
// ${filePath}應該導出一個返回對象的方法
log.warn(`${filePath} should export a method that returns an object`)
// 合並 state
const state = Object.assign({}, moduleData)
// 以函數形式導出state
return () => state
}
// 對模塊化進行處理
return normalizeModule(moduleData, filePath)
}
// 對模塊化進行處理
// moduleData - 導出數據
// filePath - 文件路徑
function normalizeModule(moduleData, filePath) {
// 如果module數據的state存在並且不是function警告提示
if (moduleData.state && typeof moduleData.state !== 'function') {
// “state”應該是返回${filePath}中的對象的方法
log.warn(`'state' should be a method that returns an object in ${filePath}`)
// 合並state
const state = Object.assign({}, moduleData.state)
// 覆蓋原有state使用函數返回
moduleData = Object.assign({}, moduleData, { state: () => state })
}
// 返回初始化數據
return moduleData
}
// 獲取store的Model
// - storeModule store數據模型
// - namespaces 命名空間名稱數組
// - 是否使用命名空間 默認值 為false
function getStoreModule(storeModule, namespaces, { isProperty = false } = {}) {
// 如果 namespaces 不存在,啟動命名空間,命名空間名稱長度1
if (!namespaces.length || (isProperty && namespaces.length === 1)) {
// 返回model
return storeModule
}
// 獲取命名空間名稱
const namespace = namespaces.shift()
// 保存命名空間中的數據
storeModule.modules[namespace] = storeModule.modules[namespace] || {}
// 啟用命名空間
storeModule.modules[namespace].namespaced = true
// 添加命名數據
storeModule.modules[namespace].modules = storeModule.modules[namespace].modules || {}
// 遞歸
return getStoreModule(storeModule.modules[namespace], namespaces, { isProperty })
}
// 合並屬性
// storeModule - 存儲模塊
// moduleData - 存儲模屬性數據
// property - 模塊名稱
function mergeProperty(storeModule, moduleData, property) {
// 如果 moduleData 不存在推出程序
if (!moduleData) return
// 如果 模塊名稱 是 state
if (property === 'state') {
// 把state數據分到模塊空間內
storeModule.state = moduleData || storeModule.state
} else {
// 其他模塊
// 合並到對應的模塊空間內
storeModule[property] = Object.assign({}, storeModule[property], moduleData)
}
}
以上就是編譯后的store
文件,大致的意思就是對store
文件進行遍歷處理,根據不同的文件使用不同的解決方案,使用命名空間掛載model
。
頁面loading
Nuxt
有提供加載Loading
組件,一下是配置。
nuxtjs.config.js
module.exports = {
loading: { color: '#3B8070' }
}
Nuxt
提供的loading
不能滿足項目需求,可能有的項目不需要這樣加載動畫,so~,就需要自己手動配置一個。添加一個loading組件 (官方示例如下,詳情可看官方文檔)引用該組件。
nuxtjs.config.js
module.exports = {
loading: '~components/loading.vue'
}
一個小插曲在Nuxt中,~與@都指向的是根目錄。
components/loading.vue
<template lang="html">
<div class="loading-page" v-if="loading">
<p>Loading...</p>
</div>
</template>
<script>
export default {
data: () => ({
loading: false
}),
methods: {
start () {
this.loading = true
},
finish () {
this.loading = false
}
}
}
</script>
第三方組件庫
項目開發過程中,難免會用到組件庫,與在Vue
中使用的時候是太一樣的,需要添加一些依賴才能正常使用。
plugins - element-ui.js
import Vue from 'vue';
import Element from 'element-ui';
import locale from 'element-ui/lib/locale/lang/en';
export default () => {
Vue.use(Element, { locale })
};
nuxtjs.config.js
module.exports = {
css: [
'element-ui/lib/theme-chalk/index.css'
],
plugins: [
'@/plugins/element-ui',
'@/plugins/router'
]
};
使用中間件
中間件Nuxt
沒有給出具體的使用文檔,而是放入了一個編輯器。這一點我感覺到了一絲絲的 差異。為什么要這樣。。。簡單的研究了一下,弄明白了大概。
在middleware
中創建想要的中間件。這里借用一下官網的例子。
middleware - visits.js
export default function ({ store, route, redirect }) {
store.commit('ADD_VISIT', route.path)
}
向上面這樣就創建好了一個中間件,但是應該怎么使用呢?在使用的時候有兩種方式,一種是全局使用,另一種是在頁面中單獨使用,文件名會作為其中間件的名稱。
++全局使用++
nuxtjs.config.js
export default {
router: {
middleware: ['visits']
}
}
頁面中單獨使用
export default {
middleware: 'auth'
}
官網中在頁面中的asyncData
中有一段這樣的代碼。
export default {
asyncData({ store, route, userAgent }) {
return {
userAgent
}
}
}
持續更新。。。
總結
Nuxt
的學習曲線非常小,就像Vue
框架一樣,已經是一個開箱即用的狀態,我們可以直接跨過配置直接開發。對配置有興趣的可以在Vue
官方文檔找到SSR
渲染文檔。