《Vue Router 与 Pinia 入门:页面跳转、动态路由、全局状态管理一篇打通》

一、写在前面

很多新手学 Vue3,前期做的小案例通常都长这样:

  • 一个 App.vue

  • 顶部几个按钮

  • 页面里切换显示一点内容

  • 练一练 v-if

  • 再练一练列表和表单

这阶段当然很有必要,因为它能帮你把基础语法打稳。

但只要你开始想做一个"稍微像样一点"的项目,比如:

  • 登录页

  • 首页

  • 详情页

  • 用户中心页

你马上就会发现,一个新的问题出现了:

页面不能永远都塞在一个组件里。

与此同时,只要页面一多、组件一多,又会出现另一个问题:

有些数据不是某个组件自己用,而是很多地方都要用。

比如:

  • 当前登录用户信息

  • 是否已登录

  • 购物车数量

  • 当前主题状态

  • 选中的菜单项

这些都不是靠简单的父传子就能优雅解决的。

所以从这里开始,你需要两样东西:

1. 路由

解决页面切换与页面组织问题

2. 状态管理

解决跨组件、跨页面共享数据问题

这就是 Vue Router 和 Pinia 出现的意义。


二、为什么 Vue 项目需要路由?

先说一个最根本的问题。

如果没有路由,你当然也能做页面切换。

比如你可以用:

  • v-if

  • v-show

  • 一个 currentPage 变量

来手动控制某个区域显示"首页"还是"详情页"。

例如:

复制代码
<template>
  <HomePage v-if="currentPage === 'home'" />
  <ProfilePage v-if="currentPage === 'profile'" />
</template>

这在非常小的练习里可以用。

但它有一个明显的问题:

它不是"真正的页面导航",只是组件显示切换。

真实项目需要的是:

  • 浏览器地址能变化

  • 用户可以通过 URL 直接访问页面

  • 可以前进后退

  • 可以收藏某个页面

  • 可以区分首页、详情页、登录页这些不同页面状态

这些能力,都不是简单的 v-if 能解决的。

所以路由的核心意义是:

让单页应用拥有"多页面导航"的能力。


三、什么是 Vue Router?

先给一个非常适合新手理解的定义:

Vue Router 是 Vue 官方的路由管理工具,用来管理"路径"和"页面组件"之间的对应关系。

你可以把它理解成:

  • 浏览器地址栏里有路径

  • Vue Router 负责规定:某个路径该显示哪个页面组件

例如:

  • / 对应首页

  • /login 对应登录页

  • /profile 对应个人中心页

所以它本质上是在做一件非常关键的事情:

地址变化 → 页面跟着切换


四、路由最核心的两件事是什么?

前期你只要先抓住两件事就够了。

1. 定义路由规则

也就是规定:

  • 哪个路径对应哪个页面组件

2. 在页面中留一个展示出口

让匹配到的页面组件显示出来

这两个点一旦通了,Vue Router 就不再抽象了。


五、一个最基础的路由配置长什么样?

通常会有一个 router/index.ts 文件,写法像这样:

复制代码
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import AboutView from '../views/AboutView.vue'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', component: HomeView },
    { path: '/about', component: AboutView }
  ]
})

export default router

这段代码先不要怕,我们拆开看。


1. routes 是什么?

它就是路由规则数组。

也就是说,这里在规定:

  • 当路径是 / 时,显示 HomeView

  • 当路径是 /about 时,显示 AboutView

所以你可以把 routes 理解成:

路径和页面组件的映射表。


2. createRouter 在干什么?

它是在创建一个路由实例。

你可以先粗略理解成:

把这些路由规则收集起来,交给 Vue 项目使用。


3. createWebHistory() 先怎么理解?

新手阶段你不用一开始钻太深。

先把它理解成:

使用更常见、更自然的浏览器路径模式。

也就是地址看起来像:

复制代码
/login
/profile
/detail/1

而不是带 # 的老式 hash 路径。

前期知道这个程度就够了。


六、路由配置完之后,项目里还要做什么?

只配路由规则还不够,还要把路由接入项目。

通常在 main.ts 里这样写:

复制代码
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

const app = createApp(App)

app.use(router)

app.mount('#app')

这里最关键的一句是:

复制代码
app.use(router)

它表示:

把路由系统挂到整个 Vue 应用上。

只有这样,项目里后面才能真正使用路由能力。


七、router-view 是什么?

这是 Vue Router 最核心的标签之一。

你可以把它理解成:

页面组件的显示出口。

也就是说,当前路径匹配到哪个页面组件,

那个页面组件就会显示在 <router-view /> 的位置。

例如:

复制代码
<template>
  <router-view />
</template>

如果当前路径是 /,它显示 HomeView

如果当前路径是 /about,它显示 AboutView

所以你可以先把它记成一句话:

router-view 决定"当前路由页面显示在哪里"。


这是另一个非常高频的标签。

它的作用是:

用来进行页面跳转。

例如:

复制代码
<template>
  <router-link to="/">首页</router-link>
  <router-link to="/about">关于页</router-link>
</template>

点击后,页面会跳转到对应路径,并且 router-view 中显示相应页面。

你可以把它理解成:

Vue Router 版本的页面跳转链接。


为什么不直接用 <a> 标签?

因为普通 <a> 标签更偏浏览器原生跳转,

router-link 是专门给 Vue 单页应用设计的导航方式,更适合项目内部页面切换。

前期你只要知道:

  • 页面内部导航,优先考虑 router-link

  • 它和路由系统是天然配套的

就够了。


九、一个完整的最小路由例子

1. router/index.ts

复制代码
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import AboutView from '../views/AboutView.vue'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', component: HomeView },
    { path: '/about', component: AboutView }
  ]
})

export default router

2. App.vue

复制代码
<template>
  <div>
    <router-link to="/">首页</router-link>
    <router-link to="/about">关于</router-link>

    <hr />

    <router-view />
  </div>
</template>

3. HomeView.vue

复制代码
<template>
  <h2>这里是首页</h2>
</template>

4. AboutView.vue

复制代码
<template>
  <h2>这里是关于页</h2>
</template>

这个例子跑起来以后,你就会真正感觉到:

  • 地址变了

  • 页面也跟着切换了

这就是 Vue Router 最基础的工作方式。


十、什么叫"声明式导航"和"编程式导航"?

这是路由学习里很常见的说法。


1. 声明式导航

就是直接在模板里写:

复制代码
<router-link to="/about">去关于页</router-link>

它更像"在页面结构中声明一个跳转入口"。

所以叫声明式导航。


2. 编程式导航

就是在 JS 逻辑里通过代码控制跳转。

例如:

复制代码
<script setup>
import { useRouter } from 'vue-router'

const router = useRouter()

const goAbout = () => {
  router.push('/about')
}
</script>

<template>
  <button @click="goAbout">跳转到关于页</button>
</template>

这里不是用户点一个 router-link

而是你在函数里通过 router.push() 主动跳转。

所以它叫编程式导航。


什么时候更适合用编程式导航?

比如这些场景:

  • 登录成功后自动跳转首页

  • 提交表单后跳转详情页

  • 删除成功后返回列表页

  • 点击某个按钮后再决定跳转

也就是说:

只要跳转是"逻辑驱动"的,而不是纯静态链接,就更适合编程式导航。


十一、动态路由是什么?

这也是非常高频的场景。

很多页面不是固定路径,而是带参数的。比如:

  • 查看 id 为 1 的商品详情

  • 查看 id 为 2 的用户详情

  • 查看某篇文章详情

这时候路径往往长这样:

复制代码
/detail/1
/detail/2
/detail/3

这里的 123 是变化的。

这种路由就叫动态路由。


1. 动态路由配置写法

复制代码
{
  path: '/detail/:id',
  component: DetailView
}

这里的 :id 表示:

这个位置是动态参数。

也就是说:

  • /detail/1

  • /detail/2

都可以匹配到这个页面组件。


2. 页面里怎么拿到这个参数?

通常这样写:

复制代码
<script setup>
import { useRoute } from 'vue-router'

const route = useRoute()

console.log(route.params.id)
</script>

这样你就能拿到路径中的 id

所以动态路由的核心思路很简单:

路径里带变量,页面里再把变量取出来。


十二、为什么多个组件共享数据时,普通 props 开始不够用了?

现在进入这一篇的第二个核心主角:Pinia。

先说一个非常现实的问题。

前面你已经学过:

  • 父传子用 props

  • 子传父用 emit

这在父子关系里非常好用。

但真实项目里很快会出现一些更复杂的情况:

  • 首页要显示当前用户名

  • 顶部导航也要显示用户名

  • 个人中心页也要显示用户名

  • 某些设置页还要改用户名

这时候你会发现:

这个数据不是某个父子链条内部的事情,而是很多页面、很多组件都要用。

如果你还靠 props 一层层传,就会变得非常麻烦。

这时候就需要一种"全局共享状态"的方案。

这就是 Pinia 的意义。


十三、什么是 Pinia?

先给定义:

Pinia 是 Vue 官方推荐的状态管理工具,用来集中管理全局共享状态。

你可以把它理解成:

  • 某些数据不是局部的

  • 而是整个项目很多地方都要用

  • 那就把它们统一放到一个专门的地方管理

比如:

  • 当前登录用户

  • 是否登录

  • 购物车商品数量

  • 主题状态

  • 权限信息

这些都特别适合放进 Pinia。


十四、为什么叫"状态管理"?

这里的"状态",你可以先简单理解成:

会影响页面显示或业务逻辑的数据。

比如:

  • 用户是否登录

  • 当前用户名

  • 当前主题是浅色还是深色

  • 当前购物车里有几件商品

这些都属于项目状态。

而"状态管理"的意思就是:

把这些共享状态统一组织、统一修改、统一使用。


十五、Pinia 最基础的结构是什么?

Pinia 里最常见的几个概念是:

  • state

  • getters

  • actions

你前期只要把这三个分清,就已经很够用了。


1. state

存放原始状态数据

例如:

  • 用户名

  • 计数值

  • 是否登录


2. getters

基于状态计算出的派生结果

例如:

  • 是否欢迎词

  • 格式化后的用户名

  • 购物车总数量

它很像你前面学过的 computed


3. actions

用来修改状态的方法

例如:

  • 登录

  • 退出登录

  • 数量加一

  • 更新用户名

所以你可以记成一句非常实用的话:

state 放数据,getters 算结果,actions 做操作。


十六、一个最简单的 Pinia store 长什么样?

例如我们建一个 stores/counter.ts

复制代码
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0
  }),
  getters: {
    doubleCount: (state) => state.count * 2
  },
  actions: {
    increment() {
      this.count++
    }
  }
})

这段代码先不要怕,我们拆开理解。


1. defineStore('counter', ...)

表示定义一个 store,名字叫 counter

你可以把 store 理解成:

一块集中管理状态的小仓库。


2. state

复制代码
state: () => ({
  count: 0
})

表示这个仓库里最原始的数据是 count


3. getters

复制代码
getters: {
  doubleCount: (state) => state.count * 2
}

表示基于 count 推导出一个派生值 doubleCount


4. actions

复制代码
actions: {
  increment() {
    this.count++
  }
}

表示这里定义了一个修改状态的方法。


十七、怎么在组件里使用 Pinia?

在组件中通常这样写:

复制代码
<script setup>
import { useCounterStore } from './stores/counter'

const counterStore = useCounterStore()
</script>

<template>
  <div>
    <p>当前 count:{{ counterStore.count }}</p>
    <p>双倍 count:{{ counterStore.doubleCount }}</p>
    <button @click="counterStore.increment()">加一</button>
  </div>
</template>

这里的意思非常直白:

  • counterStore.count 访问状态

  • counterStore.doubleCount 访问 getter

  • counterStore.increment() 调用 action

你会发现,这和组件本地状态不同的地方在于:

它不是某个组件自己私有的数据,而是一个可共享的数据源。


十八、Pinia 最适合哪些场景?

不是所有数据都应该丢到 Pinia。

这一点新手要非常注意。

Pinia 更适合这种场景:

1. 多个组件都要用

例如顶部栏和个人中心都要显示用户名。

2. 多个页面都要用

例如首页、购物车页、订单页都要用购物车数量。

3. 状态需要长期共享

例如登录状态、主题设置、权限信息。


不太适合放 Pinia 的情况

如果某个数据只是某个组件自己内部临时用一下,比如:

  • 某个输入框值

  • 某个弹窗的局部开关

  • 某个表单局部状态

那通常放组件内部就够了,没必要全局化。

所以一定要建立一个很重要的意识:

Pinia 不是"所有数据都放进去",而是"共享状态才放进去"。


十九、Vue Router 和 Pinia 分别解决了什么问题?

这一点一定要彻底分清。


Vue Router 解决的是:

页面怎么切、地址怎么变、路径和页面如何对应

它管的是"页面导航结构"。


Pinia 解决的是:

共享数据放哪里、多个组件怎么共同使用和修改状态

它管的是"全局共享状态"。


所以它们不是同一层东西,而是项目里的两个不同维度:

  • Router 管页面流转

  • Pinia 管共享数据流转


二十、一个很贴近实战的例子:登录后跳转首页,并共享用户名

这个例子能把 Router 和 Pinia 的配合感受出来。

1. 用户在登录页输入用户名并登录

2. 登录成功后:

  • 把用户名存到 Pinia

  • 再跳转到首页

3. 首页顶部栏直接读取 Pinia 中的用户名显示

这里的核心逻辑就是:

  • Router 负责跳转

  • Pinia 负责保存共享状态

你会发现,一旦项目稍微像样一点,这两个工具就会经常一起出现。


二十一、新手最常见的几个误区

这一部分非常重要。


1. 还没多页面,就硬上 Router 和 Pinia

如果你只是做一个非常小的单组件练习,

其实根本不需要急着把所有工程化能力都堆上去。

所以一定要记住:

工具是为需求服务的,不是为了看起来完整。


2. 把所有数据都扔进 Pinia

这也是典型误区。

组件自己的局部状态,没必要全局化。

否则后面反而更乱。


一定要记住:

  • router-link 是跳转入口

  • router-view 是页面显示出口

这两个角色完全不同。


4. 把动态路由理解成"多个页面文件"

其实动态路由常常还是同一个页面组件,

只是路径参数不同而已。

比如 /detail/1/detail/2

很多时候都是同一个 DetailView.vue,只不过读到的 id 不同。


5. 还没搞懂 props,就急着用 Pinia 代替一切

Pinia 不是用来替代基础组件通信的。

父子组件之间很清晰的局部传值,依然优先用:

  • props

  • emit

只有真正跨层级、跨页面共享时,Pinia 才更合适。


二十二、这一篇学完后,你应该达到什么程度?

如果你把这篇真正理解了,至少应该做到:

  • 知道为什么 Vue 项目需要路由

  • 知道 Vue Router 是干什么的

  • 知道 router-view 是页面显示出口

  • 知道 router-link 是页面跳转入口

  • 知道编程式导航和声明式导航的大致区别

  • 知道什么是动态路由参数

  • 知道为什么共享数据会需要 Pinia

  • 知道 stategettersactions 各自的职责

  • 知道 Pinia 适合共享状态,而不是所有状态

只要这些点通了,你的 Vue3 学习就已经开始真正进入"应用开发层"了。


二十三、总结

这一篇文章,我们把 Vue3 项目中两个非常关键的能力打通了:

  • Vue Router

  • Pinia

最重要的主线可以浓缩成下面几句话:

  • Vue Router 管页面跳转和路径映射

  • router-link 用来跳转

  • router-view 用来显示当前页面

  • 动态路由 让路径参数可变化

  • Pinia 管全局共享状态

  • state 放原始数据

  • getters 放派生结果

  • actions 放状态操作

如果说前面的文章让你逐渐学会了:

  • 写组件

  • 做通信

  • 看项目结构

那么这一篇的作用,就是让你开始真正具备:

把多个页面和共享状态组织成一个"应用"的能力。

这一步非常关键,因为接下来,我们就要把前面的知识真正落到项目里了。

相关推荐
发现一只大呆瓜16 小时前
SSO单点登录:从同域到跨域实战
前端·javascript·面试
发现一只大呆瓜16 小时前
告别登录中断:前端双 Token无感刷新
前端·javascript·面试
Cg1362691597417 小时前
JS-对象-Dom案例
开发语言·前端·javascript
无限大618 小时前
《AI观,观AI》:善用AI赋能|让AI成为你深耕核心、推进重心的“最强助手”
前端·后端
烛阴18 小时前
Claude Code Skill 从入门到自定义完整教程(Windows 版)
前端·ai编程·claude
lxh011318 小时前
数据流的中位数
开发语言·前端·javascript
神仙别闹18 小时前
基于NodeJS+Vue+MySQL实现一个在线编程笔试平台
前端·vue.js·mysql
zadyd19 小时前
Workflow or ReAct ?
前端·react.js·前端框架
北寻北爱21 小时前
vue2和vue3使用less和scss
前端·less·scss
IT_陈寒1 天前
Redis性能提升3倍的5个冷门技巧,90%开发者都不知道!
前端·人工智能·后端