Nuxt.js常见问题

客户端 & 服务器端

Nuxt.js前后端同构
beforeCreate()created()生命周期同时存在于客户端和服务器端

有些代码可能只能运行在客户端,就需要加判断

if (process.browser) { // 客户端代码 }

asyncData & fetch

asyncDatafetch只有在pages里面有;layoutcomponents里面都没有

过滤器

  • plugins/目录下新建文件filter.js
import Vue from 'vue' import moment from 'moment' const filters = { formatNum: (val) => { if (val > 1000) { return (val / 1000).toFixed(1) + 'k' } else if (val > 1000000) { return (val / 1000000).toFixed(1) + 'b' } else { return val } }, formatTime: (val) => { if (!val) { return ''; } const d = new Date(val); const now = new Date(); const Y1 = d.getFullYear(); const M1 = d.getMonth() + 1; const D1 = d.getDate(); const yesterday = new Date(now.getTime() - 60 * 60 * 24 * 1000); if (Y1 === now.getFullYear() && M1 === (now.getMonth() + 1) && D1 === now.getDate()) { return moment(d).format('hh:mm'); } else if (Y1 === yesterday.getFullYear() && M1 === (yesterday.getMonth() + 1) && D1 === yesterday.getDate()) { return `yesterday ${moment(d).format('hh:mm')}`; } else { return moment(d).format('yyyy-MM-dd hh:mm'); } }, formatSize: (val) => { let size = parseInt(val) size = (size / 1024).toFixed(2) if (size < 1024) { return size + 'KB' } size = (size / 1024).toFixed(2) if (size < 1024) { return size + 'MB' } size = (size / 1024).toFixed(2) return size + 'GB' } } Object.keys(filters).forEach(key => { Vue.filter(key, filters[key]) }) export default filters
  • 修改nuxt.config.js文件,添加pluginis配置
// ... module.exports = { // ... plugins: [ // ... { src: '@/plugins/filters', ssr: true }, // ... ] // ... }
  • 组件中直接使用即可

Vuex持久化

可以使用插件vuex-persistedstate

原理是将vuex里面的数据存储到localStorage里面

localStorage只存在客户端,所以只能作用在客户端

详情参考:Vue常用插件:vuex-persistedstate

  • plugins/目录下添加文件vuex-persistedstate.js
import createPersistedState from 'vuex-persistedstate' export default (context) => { createPersistedState()(context.store) }
  • 修改nuxt.config.js文件,添加pluginis配置
// ... module.exports = { // ... plugins: [ // ... { src: '@/plugins/vuex-persistedstate', ssr: false }, // ... ] // ... }
  • 如果只要部分数据自动存储。修改plugins/vuex-persistedstate.js文件
import createPersistedState from 'vuex-persistedstate' export default (context) => { createPersistedState({ reducer(obj) { // 其中 mobile, sidebar, user, deviceId, sessionId 为需要自动存储的 state const { mobile, sidebar, user, deviceId, sessionId } = obj return { mobile, sidebar, user, deviceId, sessionId } } })(context.store) }

初始化Vuex

vuex-persistedstate可以将数据持久化,但是只作用于客户端
页面刷新,asyncDatafetch请求数据的时候,可能需要vuex里面的数据(比如:token),这个时候vuex的初始值不是存储在localStorage中的值
需要将localStorage里面的值获取到

思路:

  • 页面请求时,在请求头中会携带cookie信息,我们将需要的数据放在cookie里面
  • nuxtServerInit可以让我们在asyncDatafetch之前设置vuex的值

步骤

  • 修改store/目录下的各模块文件,比如:index.js
import Cookies from 'js-cookie' export const state = () => ({ // ... user: {}, token: '', // ... }) export const mutations = { // ... setUserAvatar(state, avatar) { state.user.Avatar = avatar // avatar里面可能有 = 等特殊字符,先格式化一下再存 Cookies.set('userAvatar', encodeURIComponent(avatar)) }, setToken(state, token) { state.token = token Cookies.set('token', token) }, // ... } // ...
  • 修改store/index.js文件,添加nuxtServerInit
// ... import { cookieParse } from '../utils/utils' // ... export const actions = { nuxtServerInit({ commit }, { req }) { const cookie = req.headers.cookie if (cookie) { // 将cookie转成json对象(自己实现该方法) const cookieObj = cookieParse(cookie) const { userAvatar, token } = cookieObj // avatar里面可能有 = 等特殊字符,取出来后先转一下 userAvatar && commit('setUserAvatar', decodeURIComponent(userAvatar)) token && commit('setToken', token) } }, // ... }
  • 修改utils/utils.js文件,添加cookieParse方法
// ... export function cookieParse(cookie) { if (!cookie) { return {} } const arr = cookie.split(';') const obj = {} arr.forEach(item => { const temp = item.split('=') const key = trim(temp[0]) let value = trim(temp[1]) value = unescape(value) try { value = JSON.parse(value) } catch {} obj[key] = value }) return obj } // ...
  • 页面刷新的时候,可能不会触发mutations,我们主动调用一下,比如在layout

  • 新建mixins/目录

  • mixins/目录下新建文件layout.js

// ... export default { // ... created() { if (process.browser) { this.user.Avatar && this.$store.commit('setUserAvatar', this.user.Avatar) this.token && this.$store.commit('setToken', this.token) } }, // ... }
  • 修改layout/目录下的文件,给每个layout添加mixins
// ... import layoutMixin from '@/mixins/layout' export default { mixins: [layoutMixin], // ... }

配置多环境

思路:

  • 执行命令时带上全局变量env_config
  • 根据全局变量调用不同的配置文件
  • 修改package.json文件的scripts部分
{ "scripts": { "dev": "cross-env NODE_ENV=development env_config=dev nodemon server/index.js --watch server", "build:sit": "cross-env NODE_ENV=production env_config=sit nuxt build", "start:sit": "cross-env NODE_ENV=production env_config=sit node server/index.js", "generate:sit": "cross-env NODE_ENV=production env_config=sit nuxt generate", "build:prod": "cross-env NODE_ENV=production env_config=prod nuxt build", "start:prod": "cross-env NODE_ENV=production env_config=prod node server/index.js", "generate:prod": "cross-env NODE_ENV=production env_config=prod nuxt generate", "lint": "eslint --ext .js,.vue --ignore-path .gitignore .", } }
  • 修改nuxt.config.js文件
module.exports = { // ... env: { NODE_ENV: process.env.NODE_ENV, env_config: process.env.env_config }, // ... }
  • 在根目录下新建config/目录

  • config/目录下新建文件dev.env.js

export default { NODE_ENV: 'development', ENV_CONFIG: 'dev', urlApi: 'http://xxx', // 接口地址 urlCDN: 'http://xxx', }
  • config/目录下新建文件sit.env.js
export default { NODE_ENV: 'production', ENV_CONFIG: 'sit', urlApi: 'http://xxx', // 接口地址 urlCDN: 'http://xxx', }
  • config/目录下新建文件prod.env.js
export default { NODE_ENV: 'production', ENV_CONFIG: 'prod', urlApi: 'http://xxx', // 接口地址 urlCDN: 'http://xxx', }
  • config/目录下新建文件index.env.js(使用require的话,不用这么麻烦,直接const config = require('../config/' + process.env.env_config + '.env')
import dev from './dev.env' import sit from './sit.env' import prod from './prod.env' let config = {} switch (process.env.env_config) { case 'dev': config = dev break case 'sit': config = sit break case 'prod': config = prod break } export default config
  • 其他Vue或者js文件中使用
import config from '@/config/' console.log(config.urlApi)

全局样式分离

默认全局样式代码是直接写到页面中的
修改成打包成单独的css文件引入

  • 修改nuxt.config.js文件
module.exports = { // ... build: { // ... extractCSS: { // allChunks: true, filename: '[name].css', chunkFilename: '[id].css', ignoreOrder: true }, // ... }, // ... }

备注:开发环境页面会发生闪烁。不用管,生成环境是正常的

element-ui按需加载

  • 安装插件babel-plugin-componentnpm i babel-plugin-component --save-dev

  • 修改plugins/element-ui.js文件

import Vue from 'vue' // import Element from 'element-ui' // import locale from 'element-ui/lib/locale/lang/en' // Vue.use(Element, { locale }) // 按需引入 import { Message, Tooltip, Dialog, Menu, Submenu, MenuItem, MenuItemGroup, Tabs, TabPane, Row, Col, Button, Form, FormItem, Input, InputNumber, Radio, RadioGroup, RadioButton, Checkbox, CheckboxButton, CheckboxGroup, Switch, Select, Option, Popover, Carousel, CarouselItem, Upload, DatePicker, Scrollbar, Loading } from 'element-ui' import lang from 'element-ui/lib/locale/lang/en' import locale from 'element-ui/lib/locale' // 设置语言 locale.use(lang) // 引入组件 Vue.component(Message) Vue.use(Tooltip) Vue.use(Dialog) Vue.use(Menu) Vue.use(Submenu) Vue.use(MenuItem) Vue.use(MenuItemGroup) Vue.use(Tabs) Vue.use(TabPane) Vue.use(Row) Vue.use(Col) Vue.use(Button) Vue.use(Form) Vue.use(FormItem) Vue.use(Input) Vue.use(InputNumber) Vue.use(Radio) Vue.use(RadioGroup) Vue.use(RadioButton) Vue.use(Checkbox) Vue.use(CheckboxButton) Vue.use(CheckboxGroup) Vue.use(Switch) Vue.use(Select) Vue.use(Option) Vue.use(Popover) Vue.use(Carousel) Vue.use(CarouselItem) Vue.use(Upload) Vue.use(DatePicker) Vue.use(Scrollbar) Vue.use(Loading.directive) Vue.prototype.$loading = Loading.service Vue.prototype.$message = Message
  • 修改nuxt.config.js配置
module.exports = { // ... css: [ // 'element-ui/lib/theme-chalk/index.css', // ... ], // ... build: { // ... babel: { plugins: [ [ 'component', { 'libraryName': 'element-ui', // 'style': false // 不引入对应的css文件 'styleLibraryName': 'theme-chalk' } ] ] }, // ... }, // ... }

页面缓存

页面缓存可以使用keep-alive

添加 keep-alive

  • 修改layouts/目录下的vue文件,比如default.vue
<nuxt :key="activeDate" keep-alive :keep-alive-props="{ exclude: ['Publish'] }" />

修改页面

  • 页面打开和离开,需要使用新的生命周期activateddeactivated

  • 如果有监听的$route,需要添加路由判断

export default { // ... watch: { $route(to, from) { if (to.name === 'xxx') { // ... } }, // ... }, // ... }

处理 asyncData

打开或者刷新页面,asyncData在服务器端触发;然后页面由客户端接管,点击切换页面asyncData在客户端触发

加上keep-aliveasyncData依然会在路由切换的时候触发

处理思路

  • vuex中维护一个数组(比如arrFirst: []),该数组初始值为一个空数组
  • 某页面触发asyncData,先判断arrFirst数组中有没有这个页面路由的name
  • 如果有,表示已经执行过该页面的asyncData,直接return
  • 如果没有,执行接口请求操作,将该页面路由的namepusharrFirst数组中
  • nuxtServerInit中初始化arrFirst,刷新页面清空arrFirst
  • 备注arrFirst不要做持久化存储,否则会造成客户端和服务器端不一致
  • 修改store/index.js文件
export const state = () => ({ // ... arrFirst: [] }) export const mutations = { // ... setArrFirst(state, str) { if (state.arrFirst.indexOf(str) === -1) { state.arrFirst.push(str) } }, clearArrFirst(state) { state.arrFirst = [] } } export const actions = { nuxtServerInit({ commit }, { req }) { // 清空各个页面的first标识 commit('clearArrFirst') // ... }, // ... }
  • 修改plugins/vuex-persistedstate.js文件
import createPersistedState from 'vuex-persistedstate' export default (context) => { createPersistedState({ reducer(obj) { const { xx, xxx } = obj return { xx, xxx } // 这里不要加 arrFirst } })(context.store) }
  • 修改需要做缓存的页面,比如:pages/index.vue
// ... export default { asyncData({ app, store, route }) { if (store.state.arrFirst.indexOf(route.name) !== -1) { return } return axios .all([ // ... ]) .then( axios.spread((xxx, xxx, xxx) => { store.commit('setArrFirst', route.name) // ... return res }) ) }, // ... }

创作不易,若本文对你有帮助,欢迎打赏支持作者!

 分享给好友: