本文采用的技术框架有:
后台:Express + MongoDb
前台:Vue2.js + Nuxt.js@2.9.2
在Nuxt中发送请求有两种方案:
前后台分离的方案 (数据到页面的过程是在浏览器完成的) 网页源代码中没有数据
前台发送请求 -> 服务器处理请求 -> 前台接收到数据 -> 前台把数据渲染到页面
凡是通过js的行为获取到的数据都是前后台分离的
服务端渲染的方案
在组件加载之前先去发起请求拿到数据,提前将数据渲染在页面,然后返回的页面中将包含数据。
{isDev, route, store, env, params, query, req, res, redirect, error}
等),从而做一些简单操作。我们初次打开某个网站时,页面中会显示立即登录/注册的字样
在成功登录后,该部分会被替换为
现在的需求是,通过服务端渲染的方式将用户信息显示在页面上
在用户成功登录的时候,可以将个人信息保存至服务器的session中,也就意味着,我们似乎可以通过session获取到用户信息。
router.post("/singin", async (req, res) => {
const { username, password } = req.body;
const findUser = await User.findOne({ username, password });
//登录成功
if (findUser) {
// 个人信息保存至服务器的session中
req .session.user = findUser;
res.json({
code: 0,
msg: "登录成功"
})
}
//登录失败
else {
res.json({
code: -1,
msg: "账号或者密码出错"
})
}
})
可是,session保存在服务器中,而我们需要的是在浏览器中显示数据。显然,浏览器中拿不到服务器中的数据。
上面加粗的文字似乎存在问题,看起来写一个接口,在进入首页时通过接口请求session中的数据好像也可以,尝试一下。
请求session数据的接口:
router.get("/userInfo", (req, res) => {
if (req.session.user) {
res.json({
code: 0,
user: req.session.user,
msg: "获取用户信息成功"
})
}
else {
res.json({
code: 0,
user: null,
msg: "当前用户未登录"
})
}
})
前台页面:
import { request } from "@/utils/request";
export default {
async asyncData() {
// 封装后的axios请求方法
let result = await request.get("/users/userInfo");
console.log(result);
return {
result
}
},
};
在首页登录后刷新页面:
此时浏览器中可以成功显示登录后的数据,说明用户已经登录:
而服务器中则显示用户未登录:
此时就需要分析一下asyncData
这个函数的用法了。
asyncData既可能运行在浏览器,又可能运行在服务器。
刷新页面的时候,asyncData方法运行在在服务器,所以数据消息会在服务器中打印,通过打印我们发现获取不到session数据。
路由跳转<nuxt-link>
的时候,asyncData方法运行在浏览器,所以数据消息会在浏览器中打印,通过打印我们发现可以获取session数据。
为什么打印的位置不一样,session数据的打印也不一样呢?
这里涉及到了session
的原理:
所以,上面的问题的原因就在于:
请求从浏览器发出去的时候,浏览器会自动携带session对应的cookie发送服务器,所以服务器就可以根据cookie获取到数据
而请求从服务器发出去的时候,就没有session对应的cookie,所以获取不到登录的用户信息
在这里我绘制一张图来描述,可能会更加清晰:
在上面的错误操作中,我们采用的方法是调用asyncData
方法,这个方法在服务端中向接口发起请求,而不是浏览器发起的请求。由于cookie存在于浏览器中保存,所以用户登陆后。服务器中显示的是用户未登录。
在第二章中介绍具体的解决方法。
这个方法只能在vuex中使用,永远只会在服务器运行,永远只是首屏加载的时候执行一次
可以接收两个参数:
也就是说,在首屏加载过后,无论是通过this.$router.push
还是<nuxt-link>
跳转,该方法都不会执行,除非是再次刷新页面触发加载。
这意味着,采用nuxtServerInit
获取到的数据,在首屏加载过后无法通过上述两个方法将数据渲染到页面上。
// 负责登录的函数
submitForm() {
// 这里采用了Elemnet-ui的表单验证方法
this.$refs.loginForm.validate(async (valid) => {
//表单校验通过,可以发送请求
if (valid) {
let result = await axios.post("/users/singin", {
username: this.ruleForm.name,
password: CryptoJS.MD5(this.ruleForm.pwd).toString(),
});
// 通过window.location.href = "/"刷新浏览器,此时可以将数据渲染到页面上
result.code === 0
// ? this.$router.push('/') 无效
? (window.location.href = "/")
: (this.err = result.msg);
}
});
},
编写vuex:
import { request } from '@/utils/request'
export const state = () => ({
user: null,
})
export const mutations = {
SET_USER(state, payload) {
state.user = payload
}
}
export const actions = {
// 产品分为前端和后端
// 采用了SSR的前端可以分为 前端客户端[具体的html页面] + 前端服务端
// 这里的 nuxtServerInit 运行在 前端服务端中
// 该方法的第二个参数,可以获取到服务端的上下文对象
// 查阅文档可知,在这里可以获取到nodejs服务器发起请求时的 req 数据
async nuxtServerInit({ commit }, { req }) {
const user = req.session.user;
commit("SET_USER", user)
}
}
现在回到首页,将vuex中的数据读取出来:
<template>
<div>
<template v-if="user">
欢迎您,
<span class="username">{{ user.username }}</span>
[<nuxt-link to="/exit">退出</nuxt-link>]
</template>
<template v-else>
<nuxt-link to="/login">立即登录</nuxt-link> |
<nuxt-link to="/register">注册</nuxt-link>
</template>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
computed:{
...mapState(['user'])
}
};
</script>
此时可以成功将数据渲染在页面上。
这里仅讨论v2.12以下的版本
该方法与asyncData差不多,不过它无法将数据通过return
的方式返回页面渲染。
如果页面组件设置了 fetch
方法,它会在组件每次加载前被调用(在服务端或切换至目标路由之前)。
fetch
方法的第一个参数是页面组件的上下文对象context
,我们可以用 fetch
方法来获取数据填充应用的状态树。为了让获取过程可以异步,你需要返回一个 Promise,Nuxt.js 会等这个 promise 完成后再渲染组件。
注意: 无法在内部使用this
获取组件实例,fetch
是在组件初始化之前被调用
比如:
<template>
<Son></Son>
</template>
<script>
export default {
fetch({ store, params }) {
return axios.get('/userList').then( res => {
store.commit('ADD_USER', res.data)
})
}
}
</script>