您的位置:首页 > 其它

服务端预渲染之Nuxt(爬坑篇)

2019-04-20 12:05 513 查看

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
渲染文档。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: