当前位置: 首页 > news >正文

Vue3 整合 Pinia 和 Vue Router

今天我们将把前几天的零散组件串联起来,构建一个真正的单页应用 (SPA)
我们需要两位得力助手:

  1. Vue Router 4:负责页面跳转、路由守卫、参数传递。
  2. Pinia:负责全局状态管理(取代 Vuex),让数据在任意组件间轻松共享。

📦 一、快速安装与初始化

假设你已经有一个 Vite + Vue 3 项目:

# 安装路由和状态管理
npm install vue-router@4 pinia

1. 初始化 Pinia (src/main.js)

Pinia 的使用非常简单,只需创建一个实例并挂载到 App 上。

// src/main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia' // 引入 Pinia
import App from './App.vue'
import router from './router' // 引入稍后创建的路由const app = createApp(App)
const pinia = createPinia()app.use(pinia) // 使用 Pinia
app.use(router) // 使用 Routerapp.mount('#app')

🗺️ 二、Vue Router 4:掌控导航

Router 是 SPA 的骨架。Vue 3 中推荐使用基于组合式 API 的写法。

1. 配置路由 (src/router/index.js)

// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import UserView from '../views/UserView.vue'
import NotFound from '../views/NotFound.vue'const routes = [{path: '/',name: 'home',component: HomeView},{path: '/user/:id', // 动态路由参数name: 'user',component: UserView,props: true // 🔥 重要技巧:将 route.params 直接作为 props 传给组件},{path: '/:pathMatch(.*)*', // 404 通配符name: 'not-found',component: NotFound}
]const router = createRouter({history: createWebHistory(), // 使用 HTML5 History 模式 (URL 无 #)routes
})export default router

2. 设置入口 (src/App.vue)

<template><nav><!-- 声明式导航 --><router-link to="/">首页</router-link> |<router-link to="/user/123">用户详情 (ID: 123)</router-link></nav><!-- 路由出口:匹配的组件将渲染在这里 --><router-view />
</template><style>
/* 激活链接的样式 */
.router-link-active {font-weight: bold;color: #42b983;
}
</style>

3. 在组件中使用路由 (UserView.vue)

<script setup> 中,不再需要 this.$route,而是使用 Hooks。

<script setup>
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'// 1. 获取当前路由信息 (相当于 this.$route)
const route = useRoute()// 2. 获取路由实例用于编程式导航 (相当于 this.$router)
const router = useRouter()// 如果开启了 props: true,可以直接接收 id
defineProps({id: {type: String,required: true}
})// 或者从 route.params 获取 (如果没开 props)
// const userId = computed(() => route.params.id)const goHome = () => {router.push('/') // 跳转
}const goToUser = (id) => {router.push(`/user/${id}`)
}
</script><template><div><h2>用户详情页</h2><p>当前用户 ID: {{ id }}</p><button @click="goHome">返回首页</button><button @click="goToUser('456')">跳转到用户 456</button><!-- 显示当前所有参数 --><pre>{{ route.query }}</pre> </div>
</template>

🛡️ 进阶:路由守卫 (全局前置守卫)

常用于权限验证(如:未登录不能访问后台)。

// src/router/index.js (续)
router.beforeEach((to, from, next) => {const isAuthenticated = false // 模拟登录状态if (to.name !== 'home' && !isAuthenticated) {// 重定向到登录页,保留原目标地址next({ name: 'home', query: { redirect: to.fullPath } })} else {next()}
})

🐹 三、Pinia:轻量级状态管理

Pinia 去掉了 Vuex 中的 mutation,只保留 state, getters, actions,并且对 TypeScript 支持极佳。

1. 创建 Store (src/stores/counter.js)

// src/stores/counter.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'// 第一个参数是 Store 的唯一 ID (必填)
export const useCounterStore = defineStore('counter', () => {// 1. State (响应式数据)const count = ref(0)const name = ref('Pinia Store')// 2. Getters (计算属性)const doubleCount = computed(() => count.value * 2)// 3. Actions (方法,支持异步)function increment() {count.value++}function incrementAsync() {setTimeout(() => {count.value++}, 1000)}// 也可以写成箭头函数const decrement = () => count.value--return { count, name, doubleCount, increment, incrementAsync, decrement }
})

💡 为什么用函数式定义?
上面的写法叫 "Setup Store",它和组件的 <script setup> 逻辑完全一致,支持任意组合式 API,是 Pinia 推荐的新写法。

2. 在组件中使用 Store

<script setup>
import { storeToRefs } from 'pinia' // 🔥 关键:用于解构保持响应性
import { useCounterStore } from '../stores/counter'const counterStore = useCounterStore()// ❌ 错误做法:直接解构会丢失响应性
// const { count } = counterStore // ✅ 正确做法 1:直接通过 store 访问
// <button @click="counterStore.increment">{{ counterStore.count }}</button>// ✅ 正确做法 2:如果需要解构,使用 storeToRefs
// 这会将 count 和 doubleCount 转换为 ref,保持响应式
const { count, doubleCount, name } = storeToRefs(counterStore)
const { increment, decrement } = counterStore // 方法不需要 storeToRefs
</script><template><div class="store-demo"><h3>Pinia 状态演示</h3><p>商店名称: {{ name }}</p><p>计数: {{ count }} (双倍: {{ doubleCount }})</p><button @click="increment">+1</button><button @click="decrement">-1</button><button @click="counterStore.incrementAsync" :disabled="count > 10">异步 +1 (1秒后)</button></div>
</template>

3. 跨组件共享状态

Pinia 的核心优势:状态是单例的
如果你在 ComponentA 中调用了 increment()ComponentB 中的 count 会自动更新,无需任何额外代码(只要它们都引用了同一个 Store ID)。


🔄 四、整合实战:路由 + Store

场景:用户登录后,将用户信息存入 Pinia,并在路由守卫中检查。

1. 用户 Store (src/stores/user.js)

import { defineStore } from 'pinia'
import { ref } from 'vue'export const useUserStore = defineStore('user', () => {const token = ref(localStorage.getItem('token') || '')const userInfo = ref(null)const isLoggedIn = computed(() => !!token.value)function login(newToken, user) {token.value = newTokenuserInfo.value = userlocalStorage.setItem('token', newToken)}function logout() {token.value = ''userInfo.value = nulllocalStorage.removeItem('token')}return { token, userInfo, isLoggedIn, login, logout }
})

2. 路由守卫集成

// src/router/index.js
import { useUserStore } from '../stores/user'router.beforeEach((to, from, next) => {const userStore = useUserStore() // 在守卫中直接使用 store// 假设 /admin 需要登录if (to.path.startsWith('/admin') && !userStore.isLoggedIn) {next('/login?redirect=' + to.fullPath)} else {next()}
})

🚫 Day 4 避坑指南

  1. 解构 Store 丢失响应性

    • 现象const { count } = useCounterStore(),修改 count 视图不更新。
    • 解决:要么直接用 store.count,要么用 storeToRefs(store)
    • 例外:Actions (方法) 可以直接解构,因为它们不是响应式数据。
  2. Router 循环跳转

    • beforeEach 中调用 next() 时,务必确保不会形成死循环(例如:未登录 -> 跳登录页 -> 登录页守卫又判断未登录 -> 跳登录页...)。通常登录页本身不需要登录守卫。
  3. Devtools 插件

    • 记得安装 Vue Devtools 浏览器扩展。Pinia 和 Router 都有专门的面板,可以实时查看状态变化和路由历史,调试神器。
  4. SSR 注意事项

    • 如果你未来要做服务端渲染 (Nuxt.js),Pinia 和 Router 的初始化方式会有所不同(需要在每个请求中创建新实例),但在纯客户端 Vite 项目中,上述写法完全没问题。

🎯 今日实战练习

任务:构建一个简单的“待办事项 (Todo) 应用”原型。

  1. 路由
    • / (首页): 显示所有 Todo。
    • /completed: 仅显示已完成的 Todo。
  2. Store (useTodoStore)
    • State: todos (数组)。
    • Actions: addTodo(text), toggleTodo(id), clearCompleted()
    • Getters: completedTodos, pendingTodos
  3. 功能
    • 在首页输入框添加任务。
    • 点击任务切换完成状态。
    • 顶部导航栏切换 //completed 视图。
    • /completed 页面显示一个“清空已完成”按钮。

思考题:如果我想在刷新页面后保留 Todo 数据,应该在哪里加代码?(提示:watch + localStorage 或 Pinia 持久化插件)。


🏁 总结

至此,你已经掌握了 Vue 3 开发大型应用的完整拼图:

  • UI 层: <script setup>, Components, Props/Emits.
  • 逻辑层: Composables (useXxx).
  • 数据层: Pinia (全局状态).
  • 导航层: Vue Router (页面流转).
http://www.jsqmd.com/news/428848/

相关文章:

  • 2026年 堵漏工程厂家实力推荐榜:专业解决地下室/隧道/大坝等各类防水堵漏难题,精选优质服务商 - 品牌企业推荐师(官方)
  • 锁相放大器SR865A与SR860选型指南
  • 2026年厂房、餐饮、店铺及多元商业空间装修专业选型指南:聚焦靓滔装饰与思嫒装潢 - 品牌推荐官
  • 2026年诚信型会议预约系统优质推荐榜:工位系统服务商/工位系统订做研发公司/访客系统订研发公司/选择指南 - 优质品牌商家
  • 2026年无锡网站建设与外贸推广服务商推荐榜:专业SEO优化、宣传片拍摄及小程序开发一站式解决方案 - 品牌企业推荐师(官方)
  • 矢量网络分析仪E5080B使用说明
  • 2026年靠谱装修公司选择指南:老房翻新/工装/高端别墅场景下的头部品牌测评与选型建议 - 博客万
  • 基于51单片机的声光控制开关设计
  • 2026 日本展台设计搭建公司甄选:和风科创筑展,精益适配点亮会展新场景 - 资讯焦点
  • 基于单片机的智能抢答器设计
  • 2026年篮球架厂家推荐:纽戈(上海)实业有限公司专业供应移动/箱式/悬挂式/成人/室外全系产品 - 品牌推荐官
  • 2026 日本展厅设计搭建公司优选:和风长效筑馆,精益科创赋能品牌展厅 - 资讯焦点
  • div设置超出文本换行
  • 2026年变压器厂家推荐排行榜:干式/油浸式/光伏/充电桩变压器,S20/S22一级能耗及SCB14/SCB18干式变压器实力品牌深度解析 - 品牌企业推荐师(官方)
  • Temu合规标签模板制作要求有哪些?Temu合规标签模板制作步骤详解! - 跨境小媛
  • 圆形逆流冷却塔哪家强?2026年推荐这几家靠谱公司,闭式冷却塔/圆形逆流冷却塔,圆形逆流冷却塔供货厂家推荐 - 品牌推荐师
  • See Dance 2.0:新时代的产物
  • 基于矢量网络分析仪的总谐波失真(THD)测量方法简析
  • 加急办理:邓白氏编码3-6天闪电出码的专业代理公司机构盘点 - 速递信息
  • 零基础入门 Spring Boot:从“Hello World”到可上线的 Web 应用(小白友好全链路指南)
  • VLOOKUP函数使用方法大全总结
  • 2026年研磨仪市场大调查:全球与中国市场占有率TOP5品牌深度解析 - 品牌推荐大师1
  • 2026年 花辊雕刻机厂家推荐排行榜:专业雕刻设备,涵盖对压辊、压花辊、模切辊、刀模花辊、超声波辊、干燥造粒辊、圆柱、立式、模具及金属辊雕刻机 - 品牌企业推荐师(官方)
  • 惠州搬家服务公司、惠州设备搬迁公司、惠州货物搬运搬迁公司、惠州附近搬家公司、深圳仓库搬家公司、深圳仓库搬迁公司选择指南 - 优质品牌商家
  • 为什么央视都说了网络安全的人才缺口巨大,但还是有很多人找不到工作,难道又被专家忽悠了?
  • Vue3 Composables (逻辑复用)
  • 2026年 净化铝材/FFU龙骨/不锈钢防水槽/机电设备减震器厂家推荐榜单:洁净工程核心构件实力供应商深度解析 - 品牌企业推荐师(官方)
  • GitNexus:GitHub一周暴涨6000星!这个零服务器代码神器让AI终于能看懂你的代码了
  • day101(3.2)——leetcode面试经典150
  • 五轴数控磨床哪家好?浙江极磨技术有限公司实力解析 - 品牌推荐大师1