我是靠谱客的博主 幸福心情,最近开发中收集的这篇文章主要介绍关于vue服务端渲染 2 数据预存取,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

在我看来服务端的主要痛点就是数据的存取,有各种不同的解决方法但是哪一种都感觉不够完美。
这里通过vuex来进行服务端和客户端的数据同步,主要根据是服务端渲染完成之后如果存在store,会在window中插入一个字段来表示,客户端可以通过这个字段来直接加载。

上文里配置router,vuex的配置方式类似

先声明一个vuex的工厂函数

import Vue from 'vue'
import Vuex from 'vuex'
import {itemApi} from './api' //忽略实现细节,请求数据并返回一个promise
Vue.use(Vuex)
export function createStore(){
    return new Vuex.Store({

        state:{items:{}}
        mutations:{
            addItem(state,{id,item}){
                Vue.set(state.items,id,item)
            }
        },
        actions:{
            fetchItem({commit},id){
                //store.dispatch需要返回一个Promise来判断数据请求情况                
                return itemApi(id).then(item=>{
                    commit('addItem',{id,item})
                })
            }
        }
    })
}

在main里面加入vuex

import Vue from 'vue'
import {createRouter} from '@/router'
import {createStore} from '@/store'
import {sync} from 'vuex-router-sync' //新增的工具包,需要npm install安装,用来同步store和router
import App from '@/App' //根组件

export default ()=>{
  const router=createRouter()
  const store=createStore()
  sync(store,router)
  const app=new Vue({
    router,
    store,
    render:h=>h(App)
  })
  return {app,router,store} //返回store
}

一个需要进行数据的组件(下文中方案2时)

<template>
  <div>{{ item.title }}</div>
</template>

<script>
export default {
  asyncData ({ store, route }) {
    // 触发 action 后,会返回 Promise ,把这个Promise返回出去,判断状态
    return store.dispatch('fetchItem', route.params.id)
  },
  computed: {
    // 从 store 的 state 对象中的获取 item。
    item () {
      return this.$store.state.items[this.$route.params.id]
    }
  }
}
</script>

其实asyncData这个名字是随意定义的,因为他调用的时机需要手动编写代码实现
在server.js也就是服务端的入口文件里更改

//服务端的入口文件 server.js
import createApp from './main'

export default ctx => {
  return new Promise((resolve, reject) => {
    const { url } = ctx;
    const { app, router ,store} = createApp()
    router.push(url)
    router.onReady(() => {
      const matchs = router.getMatchedComponents()
      if (!matchs.length){
        return reject({code:404})
      }
      Promise.all(matchs.map(Component=>{ //遍历所有需要改变的组件
        if(Component.asyncData){ //判断这些组件有没有asyncData函数
          return Component.asyncData({ //返回这个Promise通all来全部处理
            store:store,
            route:router.currentRoute //传入的route为现在的route
          })
        }
      })).then(()=>{
        // 在所有预取钩子(preFetch hook) resolve 后,
        // 我们的 store 现在已经填充入渲染应用程序所需的状态。
        // 当我们将状态附加到上下文,
        // 并且 `template` 选项用于 renderer 时,
        // 状态将自动序列化为 `window.__INITIAL_STATE__`,并注入 HTML。
        ctx.state=store.state
        //成功返回app
        resolve(app)
      }).catch(reject)
  },reject)
  })
}

在服务端的数据存取很简单,客户端需要为不同的加载方式和用户体验来做不同配置。

方案1

通过组件beforeMount生命周期请求数据,这个需要每个组件的路由变化需要直接更新,也就是每次变化都需要变更url来触发组件的beforMount,因为数据在第一时间是没有请求到的,所以需要在每个组件上编写loading并把实际代码通过if-else的方式来不挂载,等待数据完成之后再挂载,优点是页面直接变化,用户可以在第一时间感觉到。缺点是需要每个组件添加加载指示器,并且如果不做判断的话,在首屏的时候服务端已经做好的数据,但是在beforeMount时又会进行一个数据的加载,为了解决这个可能需要为数据固定一个名称。

//客户端入口文件 client.js
import Vue from 'vue'
import createApp from './main'
const {app,router,store} = createApp() //增加引入store
//注入到window里的state转化成客户端可用的state
if(window.__INITIAL_STATE__){ //判断window里有没有state
  store.replaceState(window.__INITIAL_STATE__) //有的话加入到客户端中
}

//方案1
Vue.mixin({
  data(){ //全局mixin一个loading
    return {
        loading:false
    }
  },
  beforeMount(){ //在挂载之前
      const {asyncData}=this.$options 
      let data=null; //把数据在computed的名称固定为data,防止重复渲染
      try{
          data=this.data; //通过try/catch包裹取值,防止data为空报错
      }catch(e){}
      if(asyncData&&!data){ //如果拥有asyncData和data为空的时候,进行数据加载
          //触发loading加载为true,显示加载器不显示实际内容
          this.loading=true;
          //为当前组件的dataPromise赋值为这个返回的promise,通过判断这个的运行情况来改变loading状态或者进行数据的处理 (在组件内通过this.dataPromise.then保证数据存在)
          this.dataPromise=asyncData({store,route:router.currentRoute})
          this.dataPromise.then(()=>{
              this.loading=false;
          })catch(e=>{
              this.loading=false;
          })
      }else if(asyncData){
          //如果存在asyncData但是已经有数据了,也就是首屏情况的话返回一个成功函数,防止组件内因为判断then来做的操作因为没有promise报错
          this.dataPromise=Promise.resolve();
      }
  },

)

router.onReady(()=>{
  app.$mount('#app')
})

在方案1的情况下组件应该是这样的

<template>
    <加载器 v-if="loading"></加载器>
    <实际组件 v-else>
        ... ...
    </实际组件>
</template>
<script>
    export default={
        asyncData({store,route}){
            const {xx,xxx}=route.params;
            return store.dispatch('xxx',{xx,xxx})
        },
        computed:{
            data(){
                return this.$store.state.xx;
            }
        },
        beforeMount(){
            this.dataPromise.then(()=>{
                //对数据再处理
                //computed是在被调用时才会加载数据,data在初始化时不能直接调用computed的数据否则会抛出异常,可以把赋值操作放到这里
            })
        } 
    }
</script>
方案2

在路由发生变化的时候进行调用asyncData数据加载,等待数据加载完成之后再跳转到组件上,这时候数据已经完成,缺点是因为等待数据加载然后触发跳转所以过程看起来感觉更慢,需要触发全局的加载指示器。另外在服务器渲染的时候因为首屏数据传入,所以不会有问题,但是在使用纯客户端进行开发的时候,会因为首屏不触发路由中间件,所以不执行数据加载抛出错误,经过一次跳转之后才会正常。

// ...忽略无关代码
//方案2
Vue.mixin({
  //因为在不变更组件的情况下变更路由 例如 /a/1 /a/2这种情况
  //beforeResolve不会触发,所以需要在这种变更的时候也读取数据
  beforeRouteUpdate (to, from, next) {
    const { asyncData } = this.$options
    if(asyncData){
      // 这里如果有加载指示器(loading indicator),就触发
      asyncData({
        store: this.$store,
        route: to
      }).then(()=>{
        // 停止加载指示器(loading indicator)
        next()
      }).catch((e)=>{
        // 停止加载指示器(loading indicator)
        next();
      })
    }else{
      next()
    }
  }
})
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 = false
    const 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. actions需要返回一个promise
2. asyncData需要返回一个promise
3. 页面的展示时期是否可以得到数据

最后

以上就是幸福心情为你收集整理的关于vue服务端渲染 2 数据预存取的全部内容,希望文章能够帮你解决关于vue服务端渲染 2 数据预存取所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(42)

评论列表共有 0 条评论

立即
投稿
返回
顶部