Vue服务端渲染 - 数据相关

Vue服务端渲染 - 数据相关

同样,可以使用vuex

// store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export function createStore() {return new Vuex.Store({state: {count: 0},actions: {add({commit}, num) {commit('addNum', num);}},mutations: {addNum(state, num) {state.count += num;}}})
}

app.js中引入

import Vue from 'vue';
import App from './App.vue';
import CreateRouter from './router';
import { createStore } from './store';
export function createApp() {const router= CreateRouter();const store = createStore();Vue.prototype.$store = store;const app = new Vue({router,store,render: (h) => h(App),});return {app,router,store};
}

在页面中使用

<template><div><h1>首页页面h1><p>count: {{value}}p><button @click="addOne">+1button>div>
template><script>
export default {computed: {value() {return this.$store.state.count}},methods: {addOne() {this.$store.dispatch('add', 1);}}
}
script>

由于ssr中没有beforeCreatecreated阶段。对于一些请求数据则需要做一些处理。

首先,通过访问路由,来决定获取哪部分数据(组件渲染)。

在路由组件上暴露出一个自定义静态函数 asyncData。注意,由于此函数会在组件实例化之前调用,所以它无法访问 this。需要将 store 和路由信息作为参数传递进去。

store中定义message状态及方法用于接收请求数据。

export function createStore() {return new Vuex.Store({state: {message: ''},actions: {getMessage({commit}) {return axios.get('http://localhost:4000').then(res => {commit('setMessage', res.data.data.message);}).catch(e => {console.log(e)})}},mutations: {setMessage(state, msg) {state.message = msg;}}})
}

在组件中使用state


<template><div><h1>首页页面h1><p>message: {{message}}p>div>
template><script>
export default {asyncData({store, route}) {return store.dispatch('getMessage');},data() {return {message: this.$store.state.message}},
}
script>

服务端数据预取

在服务端获取数据,获取当前访问的组件,执行组件内的asyncData方法,并传入store

import { createApp } from './app';export default (context) => {return new Promise((resolve, reject) => {const {app, router, store} = createApp();const { url } = context;router.push(url);router.onReady(() => {const matchedComponents = router.getMatchedComponents();if (!matchedComponents.length) {return reject({ code: 404 })}Promise.all(matchedComponents.map(component => {if(component.asyncData) {return component.asyncData({store,route: router.currentRoute})}})).then(() => {context.state = store.state;resolve(app)}).catch(reject);}, reject)})
};

当使用 template 时,context.state 将作为 window.__INITIAL_STATE__ 状态,自动嵌入到最终的 HTML 中。而在客户端,在挂载到应用程序之前,store 就应该获取到状态。

在客户端对store进行替换。

// entry-client.js
if(window.__INITIAL_STATE__) {store.replaceState(window.__INITIAL_STATE__);
}

客户端数据预取

在客户端,处理数据预取有两种不同方式:

  1. 在路由导航之前解析数据

使用此策略,应用程序会等待视图所需数据全部解析之后,再传入数据并处理当前视图。好处在于,可以直接在数据准备就绪时,传入视图渲染完整内容,但是如果数据预取需要很长时间,用户在当前视图会感受到"明显卡顿"。因此,如果使用此策略,建议提供一个数据加载指示器 (data loading indicator)。
我们可以通过检查匹配的组件,并在全局路由钩子函数中执行 asyncData 函数,来在客户端实现此策略。注意,在初始路由准备就绪之后,我们应该注册此钩子,这样我们就不必再次获取服务器提取的数据。

router.onReady(() => {// 添加路由钩子函数,用于处理 asyncData.// 在初始路由 resolve 后执行,// 以便我们不会二次预取(double-fetch)已有的数据。// 使用 `router.beforeResolve()`,以便确保所有异步组件都 resolve。router.beforeResolve((to, from, next) => {const matched = router.getMatchedComponents(to)const prevMatched = router.getMatchedComponents(from)// 我们只关心非预渲染的组件// 所以我们对比它们,找出两个匹配列表的差异组件let diffed = falseconst activated = matched.filter((c, i) => {return diffed || (diffed = (prevMatched[i] !== c))})if (!activated.length) {return next()}// 这里如果有加载指示器 (loading indicator),就触发Promise.all(activated.map(c => {if (c.asyncData) {return c.asyncData({ store, route: to })}})).then(() => {// 停止加载指示器(loading indicator)next()}).catch(next)})app.$mount('#app')
})

这种方式只有当请求完成时才会渲染页面,如果请求过慢,容易造成卡顿。

  1. 匹配要渲染的视图后,再获取数据

此策略将客户端数据预取逻辑,放在视图组件的 beforeMount 函数中。当路由导航被触发时,可以立即切换视图,因此应用程序具有更快的响应速度。然而,传入视图在渲染时不会有完整的可用数据。因此,对于使用此策略的每个视图组件,都需要具有条件加载状态。
这可以通过纯客户端 (client-only) 的全局 mixin 来实现

Vue.mixin({beforeMount () {const { asyncData } = this.$optionsif (asyncData) {// 将获取数据操作分配给 promise// 以便在组件中,我们可以在数据准备就绪后// 通过运行 `this.dataPromise.then(...)` 来执行其他任务this.dataPromise = asyncData({store: this.$store,route: this.$route})}}
})

这两种策略是根本上不同的用户体验决策,应该根据你创建的应用程序的实际使用场景进行挑选。但是无论你选择哪种策略,当路由组件重用(同一路由,但是 paramsquery 已更改,例如,从 user/1user/2)时,也应该调用 asyncData 函数。我们也可以通过纯客户端 (client-only) 的全局 mixin 来处理这个问题:

Vue.mixin({beforeRouteUpdate (to, from, next) {const { asyncData } = this.$optionsif (asyncData) {asyncData({store: this.$store,route: to}).then(next).catch(next)} else {next()}}
})

Store 代码拆分

在大型应用程序中,Vuexstore 可能会分为多个模块。当然,也可以将这些模块代码,分割到相应的路由组件 chunk 中。

可以在路由组件的 asyncData 钩子函数中,使用 store.registerModule 惰性注册(lazy-register)这个模块

import fooStoreModule from '../store/modules/foo'export default {asyncData ({ store }) {store.registerModule('foo', fooStoreModule)return store.dispatch('foo/inc')},
}


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部