vueRouter & vueX
看到这里的朋友如果没有看过前几期,可以通过文章的链接跳转到第一期,从第一期的 vue2 语法开始学习,如果是复习的朋友,也可以看本期只学习 vueRouter & VueX
项目初始化
经过上期,我们学习了 vue cli 和组件通信以及插槽,这期来学习 vueRouter & vueX
首先这次也是使用 vue cli 创建一个项目,但是这次我们需要自己选择依赖,先选择一个文件夹,放置项目
使用cmd
vue create vue2-vuerouter
这里选择 Manually select features
把 router 和 vuex 选择上
这里通过上下键移动, 空格键选择
选择完成按回车创建项目
选择 vue2
这里选择 y
使用 history 的方式,下面会讲解 history 和 hash 方式的区别
这里不选择 eslink ,就选择第一个
选择 link on sava
选择 in package.json
这里不要选择保存历史,因为现在只是学习阶段
等待安装依赖项目
这里安装完成后,可以使用命令启动项目。
cd vue2-vuerouter
npm run serve
项目启动成功在 8080 端口,打开浏览器访问:
然后这个 Home 和 About 就可以让我们在不切换页面的情况下作页面内容的跳转,其实这是一个但也应用
hash 和 history的区别:
其实路由是有两种表现样式的:
第一种:http://localhost:8080/about (history)
优点: 看起来好看,不容易让用户察觉出这是一个单页面应用
缺点: 可能不兼容低版本浏览器
第二种:http://localhsot:/#/about (hash)
优点: 兼容低版本浏览器
缺点: 看起来不好看,用户知道是单页面应用
分析目录结构:
打开项目使用开发者工具:
vue2-vuerouter/
├── .idea/ # IDE 配置文件 (WebStorm/IntelliJ)
├── node_modules/ # 第三方依赖库 (标记为 libraryroot)
├── public/ # 静态公共资源
├── src/ # 源代码目录
│ ├── assets/ # 静态资源(图片/样式等)
│ ├── components/ # 公共组件
│ ├── router/ # 路由配置
│ ├── store/ # Vuex 状态管理
│ ├── views/ # 页面级组件
│ ├── App.vue # 根组件
│ └── main.js # 应用入口文件
├── .gitignore # Git忽略规则
├── babel.config.js # Babel配置
├── jsconfig.json # JS路径配置
├── package.json # 项目配置和依赖
├── package-lock.json # 依赖版本锁定
├── README.md # 项目说明文档
└── vue.config.js # Vue自定义配置
这里发现多了 router 还有 store 还有 view 这几个包,这是我们引入的 vueRouter 还有 Vuex
vueRouter
仔细观察,会发现,这里主页的两个页面分别是 view 包中的两个组件
也就是说,我们只要通过 vueRouter 的配置,就可以添加页面
这里我们新建一个 testView 页面试试
<script setup>
</script>
<template>
<div>
这是test测试页面
</div>
</template>
<style scoped>
</style>
找到 router/index.js 添加配置
这里我们先可以分析一下源代码
首先导入 Vue 实例, 导入 VueRouter 实例,导入 HomeView 组件,并且 Vue.use 使用开启 VueRouter
紧接着是一个数组,这个数组种放置我们的路由配置
最后 new 了一个 VueRouter , 使用 history 模式, base 是我们服务器的默认地址, routes 是上面定义的页面配置文件
最后导出 router 实例
所以 Vue cli 都帮我们做好配置了, 我们只需要添加页面
这里再 routes 数组中添加我们的页面
这里我们添加了,但是注意一个点,我们还需要一个按钮,来跳转到这个界面,这是需要我们自己定义的
在 app.vue 中添加 router-link to=“/test”
这里需要讲解一下这两个标签的作用了:
router-link to
这个标签是用来进行路由跳转的时候,需要我们自己有一个跳转的 link 位置
router-view
这个标签也就是我们 Home 或者 About 或者 Test ,他会根据我们点的标签的路由, 渲染我们在 routes 数组中配置的路由地址
动态路由:
这里为了方便演示,我们再次添加一个路由:
- 新建 ArticleView 组件
- 配置文件 routes 添加
- 添加一个router-link to
代码:
App.vue
<template>
<div id="app">
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>|
<router-link to="/test">Test</router-link>|
<router-link to="/article">Article</router-link>
</nav>
<router-view/>
</div>
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
nav {
padding: 30px;
}
nav a {
font-weight: bold;
color: #2c3e50;
}
nav a.router-link-exact-active {
color: #42b983;
}
</style>
<script setup lang="ts">
</script>
/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue'
import TestView from "../views/TestView.vue";
import ArticleView from "@/views/ArticleView.vue";
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/test',
name: 'test',
component: TestView,
},
{
path: '/article',
name: 'article',
component: ArticleView,
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
view/Article.vue
<script setup>
</script>
<template>
<div>
这是一个文章组件
</div>
</template>
<style scoped>
</style>
先看效果:
这里如何修改为动态路由呢?
- 在 routes 中的 path 添加 /: id
并且 props :true,这部很重要,可以使用 props 接收
/ : 加上你的参数
这就是一个动态路由
- router-link to 添加一个id
由于我们现在是静态的只能这样演示
我们可以在文章组件中获取到当前文章 id ,这样可以展示文章详情
嵌套路由:
对于每一个路由,都可以有子路由
这里配置一个 Article 文章组件的子路由
- 配置 routes 为路由配置 children 子路由属性
{
path: '/article/:id',
name: 'article',
component: ArticleView,
props: true,
children:[
{
path:'info1',
name:'info1',
component: info1View,
},
{
path:'info2',
name:'info2',
component: info2View,
},
]
},
- 添加 link to
<script>
export default {
name: 'ArticleView',
props:{
id:{
type:Number,
required:true
}
}
}
</script>
<template>
<div>
这是一个文章组件
<div>文章id为:{{id}}</div>
<div>
<router-link :to="`/article/${id}/info1`">info1</router-link> |
<router-link :to="`/article/${id}/info2`">info2</router-link>
</div>
<router-view></router-view>
</div>
</template>
<style scoped>
</style>
这里的 link to 大家需要注意, 由于我们的上一级是动态 id 路由,所以我们需要进行模板字符串的拼接
并且要使用 v-bind 的方式动态绑定数据,这里简写 :to
<router-link :to="`/article/${id}/info1`">info1</router-link> |
- view-router 渲染
其实我们发现,不管是使用一级路由还是二级路由,都是需要这几步
routes配置, link to 跳转位置, view-router 渲染内容
编程式导航与路由传参:
大家发现了吗,在我们这里的导航都是需要自己去手动的单击路由入口,我们也可以使用代码做页面跳转,这就是编程式导航,比如说,我们点进去 info2 页面,过三秒钟跳转到 home
我们可以在 info2 界面使用一个 vue 的生命周期函数 created 函数,这是在我们的组件创建完成,但是 Dom 尚未挂载时,可以访问数据的一个函数。
使用 this. r o u t e r 做跳转,这里的 t h i s . router 做跳转, 这里的 this. router做跳转,这里的this.router 并不是我们刚刚开始实例的 router 对象,这个对象是 Vue 帮我们创建的一个代理对象,与原对象隔离。
这里可以使用它做跳转
<script>
export default {
name: "info1View",
created() {
setTimeout(()=>{
this.$router.push({
path:'/',
query:{
id: 3
}
})
},3000);
}
}
</script>
<template>
<div>这是子路由info1</div>
</template>
<style scoped>
</style>
这里使用 push 跳转的同时通过 path + query 的方式传递参数,等同于在地址上拼接了一个
baseUrl + ?id = 3的方式
这样携带参数的方式有两种:
// ✅ 正确:path + query
this.$router.push({
path: '/user',
query: { id: '123' } // → /user?id=123
})
// ⚠️ 错误:path + params 会被忽略!
this.$router.push({
path: '/user',
params: { id: '123' } // params 无效!
})
// ✅ 正确:name + params
this.$router.push({
name: 'user',
params: { id: '123' } // → /user/123
})
如何获取携带的参数:
我们只需要在组件创建完成的时候,使用 this.$route.query.id接受
对于 this. r o u t e r 和 t h i s . router 和 this. router和this.route 这两个代理实例
我的理解是 this.$router 是包含路由行为的一个实例对象,也就是做跳转导航方法等
this.$route 这更像是一个路由信息表,存储我们的一些路由信息
导航守卫:
这里就要使用到 router 了
为了方便起见,我们可以在路由配置文件中,使用 router 对象的 beforEach 做一个导航守卫
其实导航守卫的作用就是可以让我们在路由跳转前后进行不同的操作
比如这里在每次路由跳转前都打印一些路由跳转信息
这里解释一些 to from next
to 是去到哪一个路由
from 是从哪一个路由来
next 是放行
这里可以看见我切换路由的信息
通常这个导航守卫是可以做加载动画,或者权限校验,对于一些不允许用户权限访问的页面,可以进行拦截
vueX
上一期,我们讲了组件之间传递参数的用法
- 父传子 props属性
- 子传父 使用this.$emit(自定义事件,返回的数据)
这里有一个问题,就是当我们的需要传递的组件层次很深,比如给父组件下子组件的子组件的子组件传参数,反过来也是,如果一层一层的套 props 或者 emit 事件,是不是很麻烦。
这里就可以用到全局状态管理了 vuex
当你使用 vuex 管理一个状态的时候,可以全部组件共享这个状态
回到项目,我们找到 store/index.js
可以发现这里的代码
导入 vue ,导入 vuex, 使用 vuex 在 vue 上,这不是和 router/index.js 也就是路由的使用方法类似吗?
也就是说我们只需要弄懂下面的代码是什么含义,如何使用,我们就会使用 vuex 做状态管理了
state
state:存放全局状态
这是存放全局状态的地方,你可以在这里定义全局状态
比如这里定义一个 userLoginState: “未登录”
现在这个状态就可以全局访问了,我们试试
在文章页面使用:
这样就能访问了 this.$store.state.userLoginState
代码:
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
userLoginState:"未登录"
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
}
})
Article.vue
<script>
export default {
name: 'ArticleView',
props:{
id:{
type:Number,
required:true
}
},
methods:{
getLoginState(){
return this.$store.state.userLoginState;
}
}
}
</script>
<template>
<div>
<div>用户登录态:{{getLoginState()}}</div>
这是一个文章组件
<div>文章id为:{{id}}</div>
<div>
<router-link :to="`/article/${id}/info1`">info1</router-link> |
<router-link :to="`/article/${id}/info2`">info2</router-link>
</div>
<router-view></router-view>
</div>
</template>
<style scoped>
</style>
mutations & actions
mutations: 修改全局状态同步的方式
你说他是一个全局管理状态,那我为什么一定要使用 mutation 的方式修改了,这是因为通过 mutation 修改的状态都会被我们的 vue develop 调试工具记录起来,而且一个全局状态管理库,只有通过规定的方式管理才不会很混乱
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
userLoginState:"未登录"
},
getters: {
},
mutations: {
changeLoginState(state,loginState) {
state.userLoginState = loginState;
}
},
actions: {
},
modules: {
}
})
我们需要修改可以调用mutation:
actions:修改全局状态异步的方式(异步逻辑执行完还要调用mutation进行状态更新)
这里的假设我们是需要几秒后在修改状态,这是一个异步执行逻辑, 可以在actions中定义
这里也是使用了mutation中的方法修改,在内部只需要使用 store
在外部调用这个异步方法就是 this.$store.dispatch
因为这里要使用异步,最常见的应用场景就是用户登录的时候,我们需要修改用户登录态,这个时候,首先会向后端发送请求获取用户登录态,这个请求是一个异步操作。
所以需要使用 await / async 包一层,也就是 promice
getters & modules
getters:获取state的一些属性,相当于 computed 计算属性,缓存计算
这里我们写了一个测试案例,在 getters 中打印用户登录态的字段长度
打开控制台发现只算了一次
这就是 getters,当你需要获取这个 state 的一些属性,并且需要经常调用就可以使用它
modules:模块化
如果你的项目有多个状态,你可以每一状态分一个模块管理
首先可以在 store 中新建 modules 文件
在modules文件中新建一个 a.js
然后你可以在 a 里面写上你的状态,比如这里是一个 count
注意export default 中 namespaced :true 打开命名空间
然后可以在 index.js 中导入这个模块
a.js
export default {
namespaced: true,
state:{
aCount: 1
},
mutations:{
update(state, count) {
state.aCount = count
}
}
}
index.js
import Vue from 'vue'
import Vuex from 'vuex'
import a from "@/store/modules/a";
Vue.use(Vuex)
export default new Vuex.Store({
state: {
userLoginState:"未登录"
},
getters: {
len(state) {
console.log("开始算 state 长度");
return state.userLoginState.length;
}
},
mutations: {
changeLoginState(state,loginState) {
state.userLoginState = loginState;
}
},
actions: {
changeLoginStateTime(store,loginState){
setTimeout(()=>{
store.commit('changeLoginState',loginState);
})
}
},
modules: {
a: a
}
})
最后使用:
这里的使用就多了一个却别,就是
this.$store.state.模块.状态
也就是比原来 this.$store.state.状态
到这里vuex基本使用就讲完了