如何在Vite-Vue SSR项目中正确使用Vue Router与Vuex?
在Vite官方Vue 3 SSR模板中集成Vuex与Vue Router的正确方式
我正在学习Vue SSR,基于Vite文档推荐的官方Vue 3 SSR启动模板重写项目,现在想引入Vuex,单独创建store.js管理状态,但模板里没有相关文件。我找了一些指南和示例,但和当前模板不匹配,刚接触SSR,对语法和插件差异感到困惑,想请教如何正确同时使用Vuex和Vue Router。
我已经修改了main.js、entry-server.js、entry-client.js并新增了store.js,但不确定是否正确,希望帮忙修正这些文件,让两者能正常工作。
解决方案步骤
1. 安装Vuex依赖
首先确保安装适配Vue 3的Vuex 4.x版本:
npm install vuex@next # 或 yarn add vuex@next
2. 修正store.js
调整导入和导出方式,保持与router.js一致的简洁风格:
import { createStore } from 'vuex' export function createStore() { return createStore({ state() { return { todoList: [] } }, actions: { fetchTodoList({ commit }) { // 实际项目中替换为异步请求后端数据 const todoList = [ { id: 0, text: 'Buy milk' }, { id: 1, text: 'Buy chocolate' } ] commit('setTodoList', todoList) } }, mutations: { setTodoList(state, todoList) { state.todoList = todoList } } }) }
3. 完善main.js
你的代码已经正确创建并挂载store,无需修改,保留即可:
import { createSSRApp } from 'vue' import App from './App.vue' import { createRouter } from './router' import { createStore } from './store' export function createApp() { const app = createSSRApp(App) const router = createRouter() const store = createStore() app.use(router) app.use(store) return { app, router, store } }
4. 修改entry-server.js(核心:服务端数据预取)
SSR关键是在服务端预取数据并将状态传递给客户端,避免 hydration 不匹配:
import { basename } from 'node:path' import { renderToString } from 'vue/server-renderer' import { createApp } from './main' export async function render(url, manifest) { const { app, router, store } = createApp() await router.push(url) await router.isReady() // 预取当前路由组件的数据 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.value }) } }) ) const ctx = { state: store.state // 将store状态存入上下文 } const html = await renderToString(app, ctx) const preloadLinks = renderPreloadLinks(ctx.modules, manifest) return { html, preloadLinks, state: store.state } } // 以下两个函数保持不变 function renderPreloadLinks(modules, manifest) { let links = '' const seen = new Set() modules.forEach((id) => { const files = manifest[id] if (files) { files.forEach((file) => { if (!seen.has(file)) { seen.add(file) const filename = basename(file) if (manifest[filename]) { for (const depFile of manifest[filename]) { links += renderPreloadLink(depFile) seen.add(depFile) } } links += renderPreloadLink(file) } }) } }) return links } function renderPreloadLink(file) { if (file.endsWith('.js')) { return `<link rel="modulepreload" crossorigin href="${file}">` } else if (file.endsWith('.css')) { return `<link rel="stylesheet" href="${file}">` } else if (file.endsWith('.woff')) { return ` <link rel="preload" href="${file}" as="font" type="font/woff" crossorigin>` } else if (file.endsWith('.woff2')) { return ` <link rel="preload" href="${file}" as="font" type="font/woff2" crossorigin>` } else if (file.endsWith('.gif')) { return ` <link rel="preload" href="${file}" as="image" type="image/gif">` } else if (file.endsWith('.jpg') || file.endsWith('.jpeg')) { return ` <link rel="preload" href="${file}" as="image" type="image/jpeg">` } else if (file.endsWith('.png')) { return ` <link rel="preload" href="${file}" as="image" type="image/png">` } else { return '' } }
5. 修改entry-client.js(恢复服务端状态)
客户端需要接收服务端注入的状态,初始化store时同步:
import { createApp } from './main' const { app, router, store } = createApp() // 同步服务端传递的初始状态 if (window.__INITIAL_STATE__) { store.replaceState(window.__INITIAL_STATE__) } router.isReady().then(() => { app.mount('#app') })
6. 修改服务端入口(如server.js)注入状态
在服务端生成的HTML中注入store状态,供客户端使用:
// 以Express服务器为例 app.get('*', async (req, res) => { try { const url = req.originalUrl const { html, preloadLinks, state } = await render(url, manifest) res.send(` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Vue SSR App</title> ${preloadLinks} <!-- 注入初始状态,避免XSS攻击 --> <script>window.__INITIAL_STATE__ = ${JSON.stringify(state).replace(/</g, '\\u003c')}</script> </head> <body> <div id="app">${html}</div> <script type="module" src="/src/entry-client.js"></script> </body> </html> `) } catch (e) { console.error(e) res.status(500).send('Internal Server Error') } })
7. 组件中使用示例
在页面组件中定义asyncData方法实现服务端预取:
<template> <div> <h1>Todo List</h1> <ul> <li v-for="todo in todoList" :key="todo.id">{{ todo.text }}</li> </ul> </div> </template> <script> import { computed } from 'vue' import { useStore } from 'vuex' export default { // 服务端预取数据的钩子 asyncData({ store }) { return store.dispatch('fetchTodoList') }, setup() { const store = useStore() const todoList = computed(() => store.state.todoList) return { todoList } } } </script>
内容的提问来源于stack exchange,提问作者hamid-davodi
相关产品推荐
相关产品推荐

