服务端预衬着之Nuxt - (爬坑篇)

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.jstodo.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属性则是经由过程定名空间才猎取到的。这又是怎样回事?

Nuxtstore中的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衬着文档。

    原文作者:Aaron
    原文地址: https://segmentfault.com/a/1190000018930829
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞