一、写在前面
很多新手学 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决定"当前路由页面显示在哪里"。
八、router-link 是什么?
这是另一个非常高频的标签。
它的作用是:
用来进行页面跳转。
例如:
<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
这里的 1、2、3 是变化的。
这种路由就叫动态路由。
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
这也是典型误区。
组件自己的局部状态,没必要全局化。
否则后面反而更乱。
3. 分不清 router-link 和 router-view
一定要记住:
-
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
-
知道
state、getters、actions各自的职责 -
知道 Pinia 适合共享状态,而不是所有状态
只要这些点通了,你的 Vue3 学习就已经开始真正进入"应用开发层"了。
二十三、总结
这一篇文章,我们把 Vue3 项目中两个非常关键的能力打通了:
-
Vue Router
-
Pinia
最重要的主线可以浓缩成下面几句话:
-
Vue Router 管页面跳转和路径映射
-
router-link用来跳转 -
router-view用来显示当前页面 -
动态路由 让路径参数可变化
-
Pinia 管全局共享状态
-
state放原始数据 -
getters放派生结果 -
actions放状态操作
如果说前面的文章让你逐渐学会了:
-
写组件
-
做通信
-
看项目结构
那么这一篇的作用,就是让你开始真正具备:
把多个页面和共享状态组织成一个"应用"的能力。
这一步非常关键,因为接下来,我们就要把前面的知识真正落到项目里了。