Nuxt
是解決SEO
的比較經常使用的解決方案,隨着Nuxt
也有不少坑,每當突破一個小技術點的時候,都有很大的成就感,在這段時間裏着實讓我痛並快樂着。在這裏根據我的學習狀況,所踩過的坑作了一個彙總和總結。javascript
Nuxt開發跨域css
項目可使用Nginx
來反向代理,將外來的請求(這裏也注意下將Linux
的防火牆放行相應端口)轉發的內部Nuxt
默認的3000
端口上,最簡單的配置文件以下:html
nuxtjs.config.jsvue
{ modules: [ '@nuxtjs/axios', '@nuxtjs/proxy' ], proxy: [ [ '/api', { target: 'http://localhost:3001', // api主機 pathRewrite: { '^/api' : '/' } } ] ] }
@nuxtjs/proxy
須要手動單獨安裝。java
Nuxt Store 使用ios
在Nuxt
中使用Vuex
跟傳統在Vue
中使用Vuex
還不太同樣,首先Nuxt
已經集成了Vuex
,不須要咱們進行二次安裝,直接引用就好,在默認Nuxt
的框架模板下有一個Store
的文件夾,就是咱們用來存放Vuex
的地方。es6
Nuxt
官方也提供了相關文檔,能夠簡單的過一下,可是官方文檔我看來比較潦草。vuex
根據官方文檔在store
文件下面建立兩個.js
文件,分別是index.js
和todo.js
。並在pages
文件夾下面建立index.vue
。element-ui
store - index.jsaxios
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
渲染文檔。