文章目录
- 1. asyncData异步数据请求
- 2. 代理配置
- 3. fetch网络请求
- 4. vuex
- 4.1 state中的数据展示
- 4.2 同步方法与异步方法
- 4.3 数据持久化处理
- 5. 中间件处理
1. asyncData异步数据请求
Nuxt.js 扩展了 Vue.js,增加了一个叫 asyncData 和 fetch 的方法,使得我们可以在设置组件的数据之前能异步获取或处理数据。
asyncData 方法会在组件(限于页面组件,页面组件就是写在 pages 中的组件)每次加载之前被调用。它可以在服务端或路由更新之前被调用。在这个方法被调用的时候,第一个参数被设定为当前页面的上下文对象,你可以利用 asyncData方法来获取数据,Nuxt.js 会将 asyncData 返回的数据融合组件 data 方法返回的数据一并返回给当前组件。
注意:由于asyncData方法是在组件初始化前被调用的,所以在方法内是没有办法通过 this 来引用组件的实例对象。
<template>
<div>
<h3>正在热映</h3>
<hr>
<ul>
<li v-for="item in films" :key="item.filmId">{{ item.name }}</li>
</ul>
<hr>
<nuxt-link to="/">首页</nuxt-link>
</div>
</template>
<script>
export default {
data() {
return {
films: []
}
},
// 这个函数在服务端执行,参数为一个上下文件对象
async asyncData({ $axios }) {
// 如果在此处你进行网络请求,在没有设置baseUrl或代理时,请一定要写全路径,否则得不到你想要的请求
// https://api.iynn.cn/film 域名也可以写在 nuxt.config.js 配置文件的 baseUrl 中
let ret = await $axios.get('https://api.iynn.cn/film/api/v1/getNowPlayingFilmList?cors=T&cityId=110100&pageNum=1&pageSize=10')
// console.log(ret.data.data.films)
// 返回一个对象,它就赋值到data配置中
return ret.data.data
}
}
</script>
<style lang="scss" scoped>
</style>
注意:在上面的案例中,虽然是服务端渲染,但还是需要后端做跨域处理,因为单单刷新当前请求数据的页面(或者是地址栏回车)时,由于是服务端渲染,所以不存在跨域,但是如果时通过路由跳转到当前页面时,就会出现跨域问题。
2. 代理配置
假如服务端没有帮我们做跨域处理,我们就需要在 nuxt.config.js 中写代理配置。注意写代理时不能再 baseURL 中写全路径。
axios: {
// baseURL: '/',
proxy: true
},
proxy: {
'/api': {
target: 'https://api.iynn.cn/film',
changeOrigin: true
}
},
server: {
// 改端口
port: 8080
},
3. fetch网络请求
我们现在让子组件来负责数据的展示,而父组件负责数据的请求,使用 axios 该怎么做呢?
父组件:
<template>
<div>
<h3>正在热映</h3>
<hr>
<film-item :films="films" />
<hr>
<nuxt-link to="/">首页</nuxt-link>
</div>
</template>
<script>
export default {
data() {
return {
films: []
}
},
// 参数为一个上下文件对象
async asyncData({ $axios }) {
// 如果在此处你进行网络请求,在没有设置baseUrl或代理时,请一定要写全路径,否则得不到你想要的请求
let ret = await $axios.get('/api/v1/getNowPlayingFilmList?cityId=110100&pageNum=1&pageSize=10')
return ret.data.data
}
}
</script>
<style lang="scss" scoped>
</style>
子组件:
<template>
<div>
<h3>子组件</h3>
<ul>
<li v-for="item in films" :key="item.filmId">{{ item.name }}</li>
</ul>
</div>
</template>
<script>
export default {
props: ['films']
}
</script>
<style lang="scss" scoped>
</style>
我们在第一小节中就知道,asyncData 方法只能在页面组件中使用,而子组件在 components 中,假如我们现在要将网络请求写在子组件中,该怎么做呢?这时候就需要用到 fetch方法。
父组件:
<template>
<div>
<h3>正在热映</h3>
<hr>
<film-item />
<hr>
<nuxt-link to="/">首页</nuxt-link>
</div>
</template>
<script>
export default {
}
</script>
<style lang="scss" scoped>
</style>
子组件:
<template>
<div>
<h3>子组件</h3>
<ul>
<li v-for="item in films" :key="item.filmId">{{ item.name }}</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
films: []
}
},
// fetch它可以运行在任何的组件中
async fetch() {
let ret = await this.$axios.get('/api/v1/getNowPlayingFilmList?cors=T&cityId=110100&pageNum=1&pageSize=10')
this.films = ret.data.data.films
}
}
</script>
<style lang="scss" scoped>
</style>
4. vuex
Nuxt.js 会尝试找到 src 目录(默认是应用根目录)下的 store 目录,如果该目录存在,它将做以下的事情:
- 引用 vuex 模块
- 将 vuex 模块 加到 vendors 构建配置中去
- 设置 Vue 根实例的 store 配置项
4.1 state中的数据展示
store/count.js:
// 以独立的函数方法来定义vuex中的state数据
// 函数的名称一定叫 state
export const state = () => ({
num: 100
})
pages/count/index.vue:
<template>
<div>
<h3>路由 /count</h3>
<hr>
<div>vuex中的state数据显示:{{ num }}</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
// 方式1
// num() {
// return this.$store.state.count.num
// }
// 方式2
...mapState('count', ['num']),
},
}
</script>
<style lang="scss" scoped>
</style>
4.2 同步方法与异步方法
store/count.js:
// 以独立的函数方法来定义vuex中的state数据
// 函数的名称一定叫 state
export const state = () => ({
num: 100
})
// 同步
export const mutations = {
addNum(state, payload) {
state.num += payload
}
}
// 异步
export const actions = {
asyncAddNum({ commit }, payload) {
setTimeout(() => {
commit('addNum', payload)
}, 1000);
}
}
pages/count/index.vue:
<template>
<div>
<h3>路由 /count</h3>
<hr>
<div>vuex中的state数据显示:{{ num }}</div>
<button @click="addNum">+++++</button>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
export default {
computed: {
// 方式1
// num() {
// return this.$store.state.count.num
// }
// 方式2
...mapState('count', ['num']),
// ...mapGetters('count', ['showNum'])
},
methods: {
addNum() {
// 同步
this.$store.commit('count/addNum', 1)
// 异步
// this.$store.dispatch('count/asyncAddNum', 2)
}
}
}
</script>
<style lang="scss" scoped>
</style>
推荐插件Nuxt
,它可以快速生成 vuex 相关的文件:
使用方式:按下 ctrl+shift+p 输入 nuxt 然后选择 create store ,最后输入文件名称就可以创建成功:
4.3 数据持久化处理
安装js-cookie
(客户端路由切换时使用)和cookie-parse
(服务端渲染时使用):
yarn add js-cookie cookie-parse
mock 假数据(static/login.json):
{
"code":0,
"msg":"ok",
"data":{
"uid":1000,
"token":"fewjlfjewklfewj;fewj;few;"
}
}
登录页面:
<template>
<div>
<h3>我是一个登录页面</h3>
<hr>
<div>
<input type="text" v-model="formData.username">
</div>
<div>
<input type="text" v-model="formData.password">
</div>
<hr>
<button @click="doLogin">进入系统</button>
</div>
</template>
<script>
// 这样导入,在初始时,在服务器渲染时,它也会导入,这样影响性能
// import cookie from 'js-cookie'
// 渲染环境判断的全局变量
// console.log(process.server);
// console.log(process.client);
// 性能优化所用
const cookie = process.client ? require('js-cookie') : null
export default {
layout: 'empty',
data() {
return {
formData: {
username: 'admin',
password: 'admin888'
}
}
},
methods: {
async doLogin() {
// 进行登录请求的验证
let ret = await this.$axios.get('/login.json')
// 登录成功,写入到cookie中,进行vuex数据持久化处理
cookie.set('token', ret.data.data.token)
cookie.set('uid', ret.data.data.uid)
// 写入到vuex中
this.$store.commit('user/setUserInfo', ret.data.data)
// 跳转到后台
this.$router.push('/admin')
}
}
}
</script>
<style lang="scss" scoped>
</style>
store/index.js:
export const actions = {
// 在服务器加载时,会主动执行1次
nuxtServerInit({ commit }, { req }) {
// 在服务器端得到当前的cookie数据,cookie数据是通过请求头发送给服务器
// string
// token=fewjlfjewklfewj%3Bfewj%3Bfew%3B; uid=1000
// 方案1
let cookieStr = req.headers.cookie
if (cookieStr) {
let jsonCookie = cookieStr.split(';').reduce((p, c) => {
let [key, value] = c.split('=')
p[key.trim()] = value
return p
}, {})
// 同步到vuex中 -- 解决刷新丢失问题
commit('user/setUserInfo', jsonCookie)
}
// 方案2
// try {
// let cookieStr = req.headers.cookie
// let jsonCookie = cookieStr.split(';').reduce((p, c) => {
// let [key, value] = c.split('=')
// p[key.trim()] = value
// return p
// }, {})
// // 同步到vuex中 -- 解决刷新丢失问题
// commit('user/setUserInfo', jsonCookie)
// } catch (error) {
// }
}
}
store/user/state.js:
export default () => ({
uid: 0,
token: ''
})
store/user/mutations.js:
export default {
setUserInfo(state, payload) {
state.uid = payload.uid
state.token = payload.token
}
}
5. 中间件处理
在根目录下创建 middleware 文件,每一个中间件都应该放在这个目录下。
middleware/logincheck.js:
export default ({ store, redirect }) => {
if (!store.state.user.token) {
redirect('/login')
}
}
上面这个中间件表示如果不是登录状态,就会重定向到登录页面。
注册全局中间件:
全局中间件就是每进入一个页面都会执行的中间件,相当于全局前置守卫。它需要在 nuxt.config.js 的 router 中进行配置。
router: {
// 全局中间件注册
middleware: 'checklogin',
// 动态添加路由 但是一定要注意在pages中不能有_.vue这样的文件,否则路由匹配不成功[push]
extendRoutes(routes, resolve) {
/* routes.push({
path: '/abc',
component: resolve(__dirname, 'pages/about.vue')
}) */
// 如果你用_.vue,则需要用unshift方法来动态添加路由信息
routes.unshift({
path: '/abc',
component: resolve(__dirname, 'pages/about.vue')
})
}
},
匹配布局:
相当于路由独享守卫,写在 layouts/default.vue 中。
<template>
<div>
<!-- 它可以理解为子路由 嵌套路由 -->
<!-- <h3>我是一个默认的全局布局文件</h3> -->
<!-- 如果你用了全局布局,则在此处一定要添加一个挂载点 -->
<nuxt />
</div>
</template>
<script>
export default {
// 路由独享
// middleware: 'checklogin'
}
</script>
<style lang="scss" scoped>
</style>
匹配页面:
相当于组件内守卫。
<template>
<div>
<h3>我是一个后台</h3>
<!-- 如果它是一个嵌套路由,则在此处一定要放置挂载点 -->
<nuxt-child />
</div>
</template>
<script>
export default {
// 写在组件或父组件中
middleware: 'checklogin'
}
</script>
<style lang="scss" scoped>
</style>
如果不是登录状态,就跳转到登录页面,用组件内中间件更合适。