本文采用的技术框架有:
后台:Express + MongoDb
前台:Vue2.js + Nuxt.js@2.9.2
在Nuxt中发送请求有两种方案:
-
前后台分离的方案 (数据到页面的过程是在浏览器完成的) 网页源代码中没有数据 前台发送请求 -> 服务器处理请求 -> 前台接收到数据 -> 前台把数据渲染到页面 凡是通过js的行为获取到的数据都是前后台分离的 -
服务端渲染的方案
- asyncData:写在页面组件中,获取页面中需要服务端渲染的数据,把数据return给页面 (直接在页面上使用数据)
- nuxtServerInit:在store/index.js里面写,永远在服务器运行,只会在页面首次加载的时候执行一次 (适合写一些全局共享的数据)
- fetch:写在页面组件中,获取页面中需要服务端渲染的数据,把数据存到vuex (父子组件的数据共享可以优先使用fetch)
一、asyncData
在组件加载之前先去发起请求拿到数据,提前将数据渲染在页面,然后返回的页面中将包含数据。
- 由于在客户端创建实例化之前加载,所以不能使用 this,钩子提供一个参数,可以获取上下文对象(
{isDev, route, store, env, params, query, req, res, redirect, error} 等),从而做一些简单操作。 - 只能在路由页面组件中使用(每次加载页面都会调用),在自定义组件中无效。
- 返回的数据最终将与 data 数据合并,为了保证不发生页面渲染错误,返回的键应事先在 data 里声明好(如果 template 中没有使用所需属性,则并不必声明)。
1.1 一个例子
我们初次打开某个网站时,页面中会显示立即登录/注册的字样
在成功登录后,该部分会被替换为
现在的需求是,通过服务端渲染的方式将用户信息显示在页面上
1.2 初步分析
在用户成功登录的时候,可以将个人信息保存至服务器的session中,也就意味着,我们似乎可以通过session获取到用户信息。
router.post("/singin", async (req, res) => {
const { username, password } = req.body;
const findUser = await User.findOne({ username, password });
if (findUser) {
req .session.user = findUser;
res.json({
code: 0,
msg: "登录成功"
})
}
else {
res.json({
code: -1,
msg: "账号或者密码出错"
})
}
})
可是,session保存在服务器中,而我们需要的是在浏览器中显示数据。显然,浏览器中拿不到服务器中的数据。
上面加粗的文字似乎存在问题,看起来写一个接口,在进入首页时通过接口请求session中的数据好像也可以,尝试一下。
1.2.1 编码
请求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() {
let result = await request.get("/users/userInfo");
console.log(result);
return {
result
}
},
};
1.2.2 测试
在首页登录后刷新页面:
此时浏览器中可以成功显示登录后的数据,说明用户已经登录:
而服务器中则显示用户未登录:
此时就需要分析一下asyncData 这个函数的用法了。
asyncData既可能运行在浏览器,又可能运行在服务器。
刷新页面的时候,asyncData方法运行在在服务器,所以数据消息会在服务器中打印,通过打印我们发现获取不到session数据。
路由跳转<nuxt-link> 的时候,asyncData方法运行在浏览器,所以数据消息会在浏览器中打印,通过打印我们发现可以获取session数据。
为什么打印的位置不一样,session数据的打印也不一样呢?
这里涉及到了session 的原理:
- 用户在通过接口登录后,用户信息将会被保存到服务器的session中
- session内部会创建cookie作为一个凭证,然后接口将这个凭证返回给浏览器保存
- 浏览器下次发送请求的时候会带上这个cookie凭证
所以,上面的问题的原因就在于:
在这里我绘制一张图来描述,可能会更加清晰:
在上面的错误操作中,我们采用的方法是调用asyncData 方法,这个方法在服务端中向接口发起请求,而不是浏览器发起的请求。由于cookie存在于浏览器中保存,所以用户登陆后。服务器中显示的是用户未登录。
在第二章中介绍具体的解决方法。
二、nuxtServerInit
这个方法只能在vuex中使用,永远只会在服务器运行,永远只是首屏加载的时候执行一次
可以接收两个参数:
也就是说,在首屏加载过后,无论是通过this.$router.push 还是<nuxt-link> 跳转,该方法都不会执行,除非是再次刷新页面触发加载。
这意味着,采用nuxtServerInit 获取到的数据,在首屏加载过后无法通过上述两个方法将数据渲染到页面上。
submitForm() {
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(),
});
result.code === 0
? (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>
此时可以成功将数据渲染在页面上。
三、fetch
这里仅讨论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>
|