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.jsexport const state = () => ({ counter: 0})export const mutations = { increment (state) { state.counter++ }}store - todo.jsexport 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// 引入vueimport Vue from ‘vue’// 引入vueximport 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 = ,就需要自己手动配置一个。添加一个loading组件 (官方示例如下,详情可看官方文档)引用该组件。nuxtjs.config.jsmodule.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.jsimport 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.jsmodule.exports = { css: [ ’element-ui/lib/theme-chalk/index.css’ ], plugins: [ ‘@/plugins/element-ui’, ‘@/plugins/router’ ]};使用中间件中间件Nuxt没有给出具体的使用文档,而是放入了一个编辑器。这一点我感觉到了一丝丝的 差异。为什么要这样。。。简单的研究了一下,弄明白了大概。在middleware中创建想要的中间件。这里借用一下官网的例子。middleware - visits.jsexport default function ({ store, route, redirect }) { store.commit(‘ADD_VISIT’, route.path)}向上面这样就创建好了一个中间件,但是应该怎么使用呢?在使用的时候有两种方式,一种是全局使用,另一种是在页面中单独使用,文件名会作为其中间件的名称。++全局使用++nuxtjs.config.jsexport default { router: { middleware: [‘visits’] }}页面中单独使用export default { middleware: ‘auth’}官网中在页面中的asyncData中有一段这样的代码。export default { asyncData({ store, route, userAgent }) { return { userAgent } }}持续更新。。。总结Nuxt的学习曲线非常小,就像Vue框架一样,已经是一个开箱即用的状态,我们可以直接跨过配置直接开发。对配置有兴趣的可以在Vue官方文档找到SSR渲染文档。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 命名空间名称数组// - 是否使用命名空间 默认值 为falsefunction 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。页面loadingNuxt有提供加载Loading组件,一下是配置。nuxtjs.config.jsmodule.exports = { loading: { color: ‘#3B8070’ }}Nuxt提供的loading不能满足项目需求,可能有的项目不需要这样加载动画,so