首先是要参考vue服务端渲染教程:https://ssr.vuejs.org/zh/data.html。
本文主要代码均参考教程得来。基本原理如下,拷贝的原文教程。
为了解决这个问题,获取的数据需要位于视图组件之外,即放置在专门的数据预取存储容器(data store)或"状态容器(state container))"中。首先,在服务器端,我们可以在渲染之前预取数据,并将数据填充到 store 中。此外,我们将在 HTML 中序列化(serialize)和内联预置(inline)状态。这样,在挂载(mount)到客户端应用程序之前,可以直接从 store 获取到内联预置(inline)状态。
依据这段话,需要使用vuex;使用vuex的代码如下:
1 import Vue from 'vue' 2 import Vuex from 'vuex' 3 Vue.use(Vuex) 4 // 假定我们有一个可以返回 Promise 的 5 // 通用 API(请忽略此 API 具体实现细节) 6 import { fetchItem } from '../api' 7 export function createStore () { 8 return new Vuex.Store({ 9 state: { 10 items: {} 11 }, 12 actions: { 13 fetchItem ({ commit }, id) { 14 // `store.dispatch()` 会返回 Promise, 15 // 以便我们能够知道数据在何时更新 16 return fetchItem(id).then(item => { 17 18 commit('setItem', { id, item }) 19 }) 20 } 21 }, 22 mutations: { 23 setItem (state, { id, item }) { 24 Vue.set(state.items, id, item) 25 } 26 } 27 }) 28 }
上面的api中代码:
1 import api from "create-api" 2 export function fetchItem(id) { 3 4 return new Promise(function(resolve, reject) { 5 api.get("http://www.youxuewang.com.cn/shouji/home/LoadProducts", { 6 pageno: 1, 7 pagesize: 200, 8 condstr: '社会大课堂:0' 9 }).then(function(res) { 10 resolve({ text: JSON.stringify(res)}); 11 12 }).catch(function() { 13 console.log(222222222222222); 14 }); 15 }) 16 17 //return Promise.resolve(obj) 18 }
create-api是webpack的别名也就是alias配置,实际是两个文件,服务端文件如下
1 const isProd = process.env.NODE_ENV === 'production'; 2 3 const axios = require('axios'); 4 let host = isProd ? 'http://www.youxuewang.com.cn/shouji/home/LoadProducts' : 'http://www.youxuewang.com.cn/shouji/home/LoadProducts'; 5 let cook = process.__COOKIE__ || ''; 6 let api; 7 8 axios.defaults.baseURL = host; 9 axios.defaults.timeout = 10000; 10 11 axios.interceptors.response.use((res) => { 12 if (res.status >= 200 && res.status < 300) { 13 console.log(121212,res.status ); 14 return res; 15 } 16 return Promise.reject(res); 17 }, (error) => { 18 // 网络异常 19 return Promise.reject({message: '网络异常,请刷新重试', err: error, type: 1}); 20 }); 21 22 if (process.__API__) { 23 api = process.__API__; 24 } else { 25 api = { 26 get: function(target, options = {}) { 27 return new Promise((resolve, reject) => { 28 axios.request({ 29 url: target, 30 method: 'get', 31 headers: { 32 'Cookie': cook 33 }, 34 params: options 35 }).then(res => { 36 resolve(res.data); 37 }).catch((error) => { 38 reject(error); 39 }); 40 }); 41 }, 42 post: function(target, options = {}) { 43 return new Promise((resolve, reject) => { 44 axios.request({ 45 url: target, 46 method: 'post', 47 headers: { 48 'Cookie': cook 49 }, 50 params: options 51 }).then(res => { 52 resolve(res.data); 53 }).catch((error) => { 54 reject(error); 55 }); 56 }); 57 } 58 }; 59 } 60 61 module.exports = api;
客户端用代码如下
1 const axios = require('axios'); 2 let api; 3 4 axios.defaults.timeout = 10000; 5 6 //拦截器,使用拦截器提前对axios操控,before they are handled by then or catch. 7 axios.interceptors.response.use((res) => { 8 if (res.status >= 200 && res.status < 300) { 9 console.log(22,res.status ); 10 return res; 11 } 12 return Promise.reject(res); 13 }, (error) => { 14 // 网络异常 15 return Promise.reject({message: '网络异常,请刷新重试', err: error}); 16 }); 17 18 if (process.__API__) { 19 api = process.__API__; 20 } else { 21 api = { 22 get: function(target, params = {}) { 23 const suffix = Object.keys(params).map(name => { 24 return `${name}=${JSON.stringify(params[name])}`; 25 }).join('&'); 26 const urls = `${target}?${suffix}`; 27 return new Promise((resolve, reject) => { 28 axios.get(urls, params).then(res => { 29 resolve(res.data); 30 }).catch((error) => { 31 reject(error); 32 }); 33 }); 34 }, 35 post: function(target, options = {}) { 36 return new Promise((resolve, reject) => { 37 axios.post(target, options).then(res => { 38 resolve(res.data); 39 }).catch((error) => { 40 reject(error); 41 }); 42 }); 43 } 44 }; 45 } 46 47 module.exports = api;
那么,我们在哪里放置「dispatch 数据预取 action」的代码?
我们需要通过访问路由,来决定获取哪部分数据 - 这也决定了哪些组件需要渲染。事实上,给定路由所需的数据,也是在该路由上渲染组件时所需的数据。所以在路由组件中放置数据预取逻辑,是很自然的事情。
我们将在路由组件上暴露出一个自定义静态函数 asyncData
。注意,由于此函数会在组件实例化之前调用,所以它无法访问 this
。需要将 store 和路由信息作为参数传递进去。
上面这句话,诞生了服务端渲染数据的路由组件有一个asyncData
方法,代码如下
1 <template> 2 <div>{{ item.text }}---{{fooCount}}</div> 3 </template> 4 <script> 5 // 在这里导入模块,而不是在 `store/index.js` 中 6 import fooStoreModule from '../store/modules/foo' 7 export default { 8 asyncData ({ store,route}) { 9 store.registerModule('foo', fooStoreModule) 10 //return store.dispatch('foo/inc') 11 return Promise.all([ 12 store.dispatch("fetchItem",route.params.id), 13 store.dispatch('foo/inc') 14 ]) 15 }, 16 // 重要信息:当多次访问路由时, 17 // 避免在客户端重复注册模块。 18 destroyed () { 19 this.$store.unregisterModule('foo') 20 }, 21 computed: { 22 fooCount () { 23 return this.$store.state.foo.count 24 },item () { 25 return this.$store.state.items[this.$route.params.id] 26 } 27 } 28 } 29 </script>
主要代码介绍如上,完成代码github链接:https://github.com/mstzhen/vue-ssr-axios。
预取数据演示访问地址:http://localhost:8080/item/22;
本文结束。