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>
效果预览
