Nuxt 踩坑记
最近开始学习 Nuxt 框架,写此博文记录学习中遇到的坑。
Nuxt 默认路由与自定义 API 路由
Nuxt 是一个服务端渲染框架,与普通的前后端分离不同(需要同时开两个端口进行开发),而 Nuxt 只需要开一个服务端的端口。默认是 3000。使用 Nuxt 脚手架建立一个 Express.js 模板,打开 server/index.js
,发现其中有一行为 app.use(nuxt.render)
,这行为 Vue-ssr 全部路由的捕获,在这一行下面的所有应用的路由都无法生效,因为 Express 会从上往下检测路由的可达性,一旦有就停止搜索。所以自定义的 API 路由需要放在这行上面。
// bind routes
require('../routes/index')(app)
// Give nuxt middleware to express
app.use(nuxt.render)
内置的axios 与 asyncData
使用 axios 获取数据并挂载
Nuxt 中内置了 axios,并挂载在 Vue 实例中的 $axios
上,通过在 nuxt.config.js 添加配置可以增加前缀,代理等。
axios: {
prefix: '/api/', // 前缀
proxy: true // 将通过 proxy 对象进行转发
},
proxy: {
'/api/': 'http://api.example.com',
'/api2/': 'http://api.another-website.com'
}
一般的我们可以在 this.$axios
获取到 axios 实例,如果你指定了 prefix 属性,在每次请求地址时会自动加上你指定的前缀。
注意:在asyncData中用 this 取得 Vue 实例,因为这时出于 BeforeCreate,你可以通过接受一个参数来取得
async asyncData ({ app }) {
const { data } = await app.$axios.get('/user')
return { user: data.data }
},
data () {
return {}
}
解构属性 app
,app中取得 axios 实例。在 async 中返回的对象将直接挂载到 data 上。如果 data 中原先有相同的键,将会被覆盖。
axios 拦截器
Nuxt/axios 同样为我们提供了拦截器,与原生的大同小异。
首先在 plugins
中新建一个 axios.js
的文件用于指定 axios 附加配置。
由于没有 axios 实现,所以我们先要接收 axios 实例。
export default ({ $axios }) => {
$axios.onRequest((config) => {
console.log('Making request to ' + config.url)
})
$axios.onError((error) => {
console.log('error.' + error.response + err.response.status)
})
}
不用的是,请求拦截器用 onRequest
代替,响应拦截器用 onResponse
代替,错误拦截器可以用 onError
代替。
除此之外,一共提供了5个拦截器。
onRequest: [Function: bound onRequest],
onResponse: [Function: bound onResponse],
onRequestError: [Function: bound onRequestError],
onResponseError: [Function: bound onResponseError],
onError: [Function: bound onError],
Nuxt 中的子路由
在 Vue 中,我们可以使用在父组件中引入 <router-view />
的标签创建一个子路由视图,然后通过 vue-router 来控制 router-view 的显示。
在 Nuxt 中,要实现这样的效果,只需要引入 <nuxt-child />
,在 Nuxt 中路由自动生成,文件夹即父路由,文件夹里的即子路由,在外层文件夹中加入一个与路由同名的 Vue 文件即可。
pages
index // index 文件夹
child.vue // index 中的子路由
index.vue // index 父路由
Vuex 与 Nuxt
在 Nuxt 中使用 Vuex,只需要在 store
文件夹下建立 index.js
即可。
切记要重启环境。
否则会出现 "(error during evaluation)"
的错误。
Nuxt 中的 vuex 会根据文件自动分成若干个模块。这里说几个我遇到的问题。
/store/viewport.js
export const state = () => ({
viewport: null
})
export const actions = {
updateViewport({ state }) {
state.viewport = {
w: window.innerWidth,
h: window.innerHeight,
mobile: window.innerWidth <= 568,
laptop: window.innerWidth <= 768,
desktop: window.innerWidth <= 1024
}
}
}
export default { state, actions }
store/viewport.js
是一个模块。在 Nuxt 中每个模块又被设定为 namespaced: true
,直接用...mapActions(['viewport'])
是不行的,需要加上空间名。这可能会导致错误。[vuex] unknown action type
...mapActions({
updateViewport: 'viewport/updateViewport'
})
在模块中,state 应返回一个函数,函数返回一个对象。否则会有一个警告。
Nuxt 中默认在开发环境中设定了严格模式,在严格模式下外部不能直接调用 action 去改变 state 的值。
Error: form binding with Vuex - Do not mutate vuex store state outside mutation handlers
我们需要在 store/index.js
下关闭严格模式。
export const strict = false
首次请求渲染页面与 SSR
首次请求的过程总体来说分为以下步骤:
所以注意的是,第一次请求的时候完全是在服务端完成渲染的,在 axios 中根本拿不到 window
localStorage
这些对象的,因为在服务器里这些对象根本不存在。
所以我们在配置 axios
的时候,需要关闭 ssr 模式。
在 nuxt.config.js
中,修改为
plugins: [
{
src: '~/plugins/axios', // axios 配置文件路径
ssr: false
}
],
首次请求携带中文的路由报错
Request path contains unescaped characters
Nuxt
原因是在首次加载的时候是服务端渲染页面的,通过 http
模块去请求接口,携带中文的接口请求时会报错,需要转码一下。
来到前端 api 文件,在请求地址前面加上 encodeURI
函数。如
async getWithSlug(category, slug) {
const { data } = await $axios.get(encodeURI(`posts/${category}/${slug}`))
return data
}
按需引入 ElementUI
首先建立 plugins/element-ui.js
,在上面加你需要引入的组件
import Vue from 'vue'
import { Message } from 'element-ui'
import 'element-ui/lib/theme-chalk/icon.css'
import 'element-ui/lib/theme-chalk/message.css'
Vue.use(Message)
在 nuxt.config.js
中加入到插件列表。
当然这还不够,因为 webpack 打包的时候还是会全部引入。
我们需要使用 babel 的 babel-plugin-component
组件。
yarn add -D babel-plugin-component
然后在 nuxt.config.js 中加入 babel 配置。
如果你没有这个对象,就加一个。但是这个列表一定要注意了,新版 babel 必须要,否则会报错。
babel: {
plugins: [
[
'component',
{
libraryName: 'element-ui',
styleLibraryName: 'theme-chalk'
}
]
]
}
webpack 按需加载 Moment.js 时区
一般的使用 Moment.js 都会引入全部 locale 文件,占了差不多 70% 的大小。但是一般我们只需要一个就完事了。
同样在 webpack 中配置,在 nuxt.config.js
中加入 extend
对象,配置 webpack
加入
config.plugins.push(
new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn/)
)
优化后:
未待完续。