SSR学习笔记(四) 数据预取

149 阅读1分钟

vuex占位 index.html

<script>
  window.__INITIAL__STATE__ = '<!--vuex-state-->'
</script>

根据占位符替换数据 server.js

const { appHtml, state } = await render(url)
  const html = template.replace(`<!--ssr-outlet-->`, appHtml)
    .replace('\'<!--vuex-state-->\'', JSON.stringify(state))

入口文件暴露出store main.js

export const createApp = () => {
  xxxxx
  return { app, router, store }
}

客户端入口替换state src/entry-client.js

const { app, router, store } = createApp()

if (window.__INITIAL__STATE__) {
  store.replaceState(window.__INITIAL__STATE__)
}

服务端入口匹配路由返回asyncData src/entry-server.js

xxxxx

export async function render(url) {
  xxxxx

  const matchedComponents = router.currentRoute.value.matched.flatMap(record =>
    Object.values(record.components)
  )
  await Promise.all(matchedComponents.map(Component => {
    if (Component.asyncData) {
      return Component.asyncData({
        store,
        route: router.currentRoute
      })
    }
  }))

  const appHtml = await renderToString(app)
  const state = store.state
  return {appHtml, state}
}

store中调用预取接口数据 src/store/index.js

import { createStore } from 'vuex'
import { ArticleApi } from "@/api";

export const createSSRStore = () => {
  return createStore({
    state: {
      homeArticles: []
    },
    mutations: {
      setHomeArticles(state, payload) {
        state.homeArticles = payload
      }
    },
    actions: {
      async getArticles({ commit }) {
        let res = await ArticleApi.getArticleList();
        commit('setHomeArticles', res.data)
      }
    }
  })
}

页面中通过asyncData数据预取 views/home/homeIndex.vue

<template>
  <div class="home">
    <Articles />
  </div>
</template>

<script>
import Articles from "./articles.vue";
export default {
  components: {
    Articles,
  },
  setup() {
    return {};
  },
  asyncData({ store, route }) {
    return store.dispatch("getArticles");
  },
};
</script>

<style lang="scss" scoped></style>

抽离文章列表,方便使用script setup

views/home/articles.vue

<template>
  <div
    class="articles"
    @click="toArticleDetail"
    v-for="(item, index) in articleList"
    :key="index"
  >
    <div class="meta">
      <div class="item">{{ item.date }}</div>
      <div class="item">{{ item.name }}</div>
      <div class="item">{{ item.zip }}</div>
    </div>
    <div class="preview">
      <div class="left">
        <div class="title overflow-one">{{ item.title }}</div>
        <div class="intro overflow-three">{{ item.intro }}</div>
      </div>
      <img :src="item.image" alt="" />
    </div>
    <div class="actions">
      <div class="item">
        <span class="iconfont icon-view"></span>
        <span>{{ item.viewed }}</span>
      </div>
      <div class="item">
        <span class="iconfont icon-icon"></span>
        <span>{{ item.like }}</span>
      </div>
      <div class="item">
        <span class="iconfont icon-shoucang"></span>
        <span>{{ item.favorite }}</span>
      </div>
    </div>
  </div>
</template>

<script setup>
import { computed } from "@vue/runtime-core";
import { useStore } from "vuex";
import { useRouter } from "vue-router";

const store = useStore();
const router = useRouter();
const articleList = computed(() => store.state.homeArticles);

const toArticleDetail = () => {
  router.push("/article");
};
</script>

<style lang="scss" scoped> xxxxx </style>

效果预览

image.png