Vue.js 前端开发基础复习串讲之 02-路由与状态管理
Vue Router
路由概述
前端路由是一种在单页应用(SPA)中管理页面导航的技术。其主要作用是根据用户的操作(如点击链接)动态地加载和显示不同的页面内容,而无需重新加载整个页面。
Vue 中的路由属于前端路由,它有两种常见的模式,分别是 Hash 模式和 History 模式。
Hash 模式的前端路由通过 URL 中从 # 号开始的部分实现不同组件之间的切换,# 号表示 Hash 符,# 号后面的值称为 Hash 值,该值将用于进行路由匹配。
History 模式的 URL 地址与后端路由的 URL 地址的风格一致,可以通过 URL 地址中的路径进行路由匹配。
Vue Router 初体验
Vue Router 是 Vue 官方推出的路由管理器,主要用于管理 URL,实现 URL 和组件的对应,通过 URL 进行组件之间的切换。
Vue Router 3.x 文档:https://v3.router.vuejs.org/zh/
在 Vue CLI 项目中使用 Vue Router,推荐在 GUI 中为项目安装 @vue/cli-plugin-router 插件。在创建 Vue CLI 项目时,也可以勾选 Vue Router 插件。
示例:安装 Vue Router
终端执行:
vue ui在 GUI 中,为项目添加 @vue/cli-plugin-router 插件:
示例效果:
在 Vue CLI 项目中添加了 @vue/cli-plugin-router 插件后,在 IDEA 中查看项目结构的变化。
src/views:用于存放 Vue 视图组件。所有的 Vue 视图组件本质上都是 Vue 普通组件。
<template>
<!-- ... -->
</template>
<script>
export default {
// ...
}
</script>
<style scoped>
</style>src/router/index.js:Vue Router 路由插件的配置文件,该文件的代码发生变化。
// 导入Vue、Vue Router
import Vue from 'vue'
import VueRouter from 'vue-router'
// 导入了HomeView组件
import HomeView from '../views/HomeView.vue'
// 对于脚手架项目,需要手动调用Vue.use安装路由插件
Vue.use(VueRouter)
// 配置一组路由
const routes = [
{
path: '/', // 请求路径
name: 'home', // 路由对象名称
component: HomeView // 路由对应的组件对象
},
{
path: '/about',
name: 'about',
// 使用函数式写法,实现路由懒加载
// 当路由被触发时,该组件才会被异步加载
component: () => import('../views/AboutView.vue')
}
]
// 实例化路由管理者
const router = new VueRouter({
mode: 'hash', // 设置路由模式,默认为hash,可选hash/history
routes
})
// 对外抛出路由管理者
export default routersrc/main.js:项目的入口 JavaScript 文件。
// ...
// 导入路由配置文件
import router from './router'
new Vue({
// 将路由管理者交给Vue实例
router,
render: h => h(App)
}).$mount('#app')了解了项目结构的变化后,可以尝试使用 Vue Router 实现路由跳转。
示例:Vue Router 初体验
首页组件(src/views/HomeIndex.vue):
<script>
export default {
name: "HomeIndex"
}
</script>
<template>
<div>
<h3>首页</h3>
<p>欢迎访问多仔的首页...</p>
</div>
</template>
<style scoped>
</style>关于我们组件(src/views/AboutUs.vue):
<script>
export default {
name: "AboutUs"
}
</script>
<template>
<div>
<h3>关于我们</h3>
<p>多仔你是我的神...</p>
</div>
</template>
<style scoped>
</style>路由配置文件(src/router/index.js):
const routes = [
{
path: '/',
name: 'HomeIndex',
component: () => import('../views/HomeIndex.vue')
},
{
path: '/about',
name: 'AboutUs',
component: () => import('../views/AboutUs.vue')
}
]根组件(src/App.vue):
<template>
<div>
<!--
<router-link>:定义路由跳转链接,相当于a标签
to:路由跳转地址,相当于a标签的href属性
tag:将<router-link>标签渲染成什么标签,默认是a标签
-->
<router-link to="/" tag="button">首页</router-link>
<router-link to="/about" tag="button">关于我们</router-link>
<!--
<router-view>:路由视图占位符,渲染对应的路由组件
-->
<router-view></router-view>
</div>
</template>示例效果:
嵌套路由
嵌套路由就是在一个路由中嵌套一组子路由,嵌套路由主要由页面结构决定,实际项目中的应用界面通常由多层嵌套的组件组合而成,为了使多层嵌套的组件能够通过路由访问,路由也需要具有嵌套关系,也就是在路由里面嵌套它的子路由。
示例:嵌套路由
联系我们组件(src/views/ContactUs.vue):
<script>
export default {
name: "ContactUs"
}
</script>
<template>
<div>
<h3>联系我们</h3>
<p>duozai.cn</p>
</div>
</template>
<style scoped>
</style>资质荣誉组件(src/views/HonorUs.vue):
<script>
export default {
name: "HonorUs"
}
</script>
<template>
<div>
<h3>资质荣誉</h3>
<p>曾经获得码神称号...</p>
</div>
</template>
<style scoped>
</style>路由配置文件(src/router/index.js):
const routes = [
// ...
{
path: '/about',
name: 'AboutUs',
component: () => import('../views/AboutUs.vue'),
children: [
// 子路由列表
// 当使用children属性定义子路由匹配规则时,子路由的path属性前不要带/
// 否则会永远以根路径开始请求
// 子路由地址 = 父路由 + "/" + 子路由
{
path: 'contact',
name: 'ContactUs',
component: () => import('../views/ContactUs.vue')
},
{
path: 'honor',
name: 'HonorUs',
component: () => import('../views/HonorUs.vue')
}
]
}
]关于我们组件(src/views/AboutUs.vue):
<template>
<div>
<h3>关于我们</h3>
<p>多仔你是我的神...</p>
<!--
定义子路由跳转链接和子路由视图占位符
-->
<router-link to="/about/contact">联系我们</router-link>
<router-link to="/about/honor">资质荣誉</router-link>
<router-view></router-view>
</div>
</template>示例效果:
命名路由
Vue-Router 提供了一种隐式的引用路径,即命名路由,在创建路由时给路由指定 name 名称,链接路由跳转时可以通过路由的名称取代路由地址。
示例:命名路由
根组件(src/App.vue):
<template>
<div>
<!-- ... -->
<!--
:to:绑定要跳转的路由对象
name:和路由的name一致
-->
<router-link :to="{name: 'AboutUs'}" tag="button">关于我们(通过命名路由跳转)</router-link>
<router-view></router-view>
</div>
</template>示例效果:
路由传参
Vue-Router 在实现路由跳转时,可以传递参数。
使用 query 传参,类似于 GET 请求,参数在地址栏可见,页面刷新后数据不丢失。
示例:query 传参
根组件(src/App.vue):
<template>
<div>
<router-link to="/" tag="button">首页</router-link>
<!--
query传参:类似于GET请求
直接在URL上拼接查询字符串
-->
<router-link to="/about?id=7&name=duozai" tag="button">关于我们</router-link>
<router-view></router-view>
</div>
</template>关于我们组件(src/views/AboutUs.vue):
<template>
<div>
<h3>关于我们</h3>
<p>多仔你是我的神...</p>
<!--
获取参数:this.$route.query.xxx
-->
<p>传递的id参数:{{this.$route.query.id}}</p>
<p>传递的name参数:{{this.$route.query.name}}</p>
</div>
</template>示例效果:
使用 name + params 传参,类似于 POST 请求,参数在地址栏不可见,页面刷新后数据丢失。
示例:name + params 传参
根组件(src/App.vue):
<template>
<div>
<router-link to="/" tag="button">首页</router-link>
<!--
params传参:类似于POST请求
只能通过name+params进行传递,不能使用路径跳转传递
-->
<router-link :to="{
name: 'AboutUs',
params: {
id: 1,
name: '张三'
}
}" tag="button">关于我们</router-link>
<router-view></router-view>
</div>
</template>关于我们组件(src/views/AboutUs.vue):
<template>
<div>
<h3>关于我们</h3>
<p>多仔你是我的神...</p>
<!--
获取参数:this.$route.params.xxx
-->
<p>传递的id参数:{{this.$route.params.id}}</p>
<p>传递的name参数:{{this.$route.params.name}}</p>
</div>
</template>示例效果:
使用路由占位符 + params 传参,类似于 RESTFul 风格。
示例:路由占位符 + params 传参
路由配置文件(src/router/index.js):
const routes = [
// ...
{
// 定义路由占位符:xxx (动态路由)
// 路由格式:/about/2/李四
path: '/about/:id/:name',
name: 'AboutUs',
component: () => import('../views/AboutUs.vue')
}
]根组件(src/App.vue):
<template>
<div>
<router-link to="/" tag="button">首页</router-link>
<!--
路由占位符+params传参:类似于RESTFul风格
-->
<router-link to="/about/2/李四" tag="button">关于我们</router-link>
<router-view></router-view>
</div>
</template>关于我们组件(src/views/AboutUs.vue):
<template>
<div>
<h3>关于我们</h3>
<p>多仔你是我的神...</p>
<!--
获取参数:this.$route.params.xxx
-->
<p>传递的id参数:{{this.$route.params.id}}</p>
<p>传递的name参数:{{this.$route.params.name}}</p>
</div>
</template>示例效果:
编程式导航
在 Vue 中,页面有两种导航方式:
- 声明式导航:使用
标签定义导航链接的方式。 - 编程式导航:使用全局路由实例的方法 $router.xxx() 实现导航链接的方式。
编程式导航的基本用法:
// 路由跳转
$router.push(跳转路径或跳转路由对象)
// 路由替换
$router.replace(跳转路径或跳转路由对象)
// 路由前进和后退
$router.go(n)示例:编程式导航
根组件(src/App.vue):
<template>
<div>
<router-link to="/" tag="button">首页</router-link>
<button @click="$router.push('/about/2/李四')">关于我们</button>
<router-view></router-view>
</div>
</template>示例效果:
Vuex
Vuex 概述
Vuex 是一个专为 Vue 开发的状态(即数据)管理库,它采用集中式存储的方式管理应用的所有组件的状态,解决了多组件数据通信的问题,使数据操作更加简洁。
Vuex 3.x 官方文档:https://v3.vuex.vuejs.org/zh/
Vuex 初体验
在 Vue CLI 项目中使用 Vuex,推荐在 GUI 中为项目安装 @vue/cli-plugin-vuex 插件。在创建 Vue CLI 项目时,也可以勾选 Vuex 功能。
示例:安装 Vuex
终端执行:
vue ui在 GUI 中,为项目添加 @vue/cli-plugin-vuex 插件:
在 Vue CLI 项目中添加了 @vue/cli-plugin-vuex 插件后,在 IDEA 中查看项目结构的变化。
src/store/index.js:Vuex 状态管理插件的配置文件。
// 导入Vue、Vuex
import Vue from 'vue'
import Vuex from 'vuex'
// 手动安装Vuex插件
Vue.use(Vuex)
// 实例化Vuex.Store即Vuex的数据仓库
// 并将其导出
export default new Vuex.Store({
state: {
// state:用于存放状态即数据
},
getters: {
// getters:用于计算属性
},
mutations: {
// mutations:用于修改state中的数据
},
actions: {
// actions:用于处理异步操作
},
modules: {
// modules:用于将数据仓库拆分为多个模块
}
})src/main.js:项目的入口 JavaScript 文件,该文件的代码发生变化。
// 导入Vuex的配置文件
import store from './store'
new Vue({
router,
// 将Vuex数据仓库交给Vue实例
store,
render: h => h(App)
}).$mount('#app')了解了项目结构的变化后,可以尝试使用 Vuex 实现状态管理。
示例:Vuex 初体验
仓库配置文件(src/store/index.js):
export default new Vuex.Store({
state: {
title: 'Hello Vuex',
count: 0
},
// ...
})根组件(src/App.vue):
<template>
<div>
<!--
通过this.$store.state.xxx访问仓库中的数据
-->
<h3>{{this.$store.state.title}}</h3>
<p>count is {{this.$store.state.count}}</p>
</div>
</template>示例效果:
Vuex mutations
在 Vuex 中,不能直接修改 store 中的数据,必须调用相应的事件处理方法处理 state 数据。如果直接修改 store 中的数据,视图页面会发生变化,但 store 中的数据实际上并没有发生变化。
示例:直接修改 store 中的数据
根组件(src/App.vue):
<template>
<div>
<h3>{{this.$store.state.title}}</h3>
<p>count is {{this.$store.state.count}}</p>
<button @click="changeTitle">title change</button>
</div>
</template>
<script>
export default {
methods: {
changeTitle() {
this.$store.state.title = 'Hello duozai'
}
}
}
</script>示例效果:
mutations 选项用来定义事件处理方法,用于处理 state 数据。调用 mutations 中的方法,就可以完成数据更新。
示例:使用 mutations 提交数据更新
仓库配置文件(src/store/index.js):
export default new Vuex.Store({
// ...
mutations: {
// state:Vuex.Store.state对象
// param:要修改的新数据,可以不传
updateTitle(state, param) {
state.title = param
},
updateCount(state) {
state.count++
}
},
// ...
})根组件(src/App.vue):
<template>
<div>
<h3>{{this.$store.state.title}}</h3>
<p>count is {{this.$store.state.count}}</p>
<button @click="changeTitle">title change</button>
<button @click="changeCount">count++</button>
</div>
</template>
<script>
export default {
methods: {
changeTitle() {
// 通过this.$store.commit()调用mutations中的更新数据的方法
// 参数1:调用的mutations方法名
// 参数2:新的参数的值,可以不传
this.$store.commit('updateTitle', 'Hello, duozai')
},
changeCount() {
this.$store.commit('updateCount')
}
}
}
</script>示例效果:
mutations 中定义的方法是同步函数,组件数据发生变化时,触发 mutations 中的事件处理方法来更新数据,这是一种同步更新,在 mutations 中不能有异步操作。
示例:使用 mutations 异步更新数据
仓库配置文件(src/store/index.js):
export default new Vuex.Store({
// ...
mutations: {
updateTitle(state, param) {
// 模拟异步操作
setTimeout(function () {
state.title = param
}, 3000)
},
// ...
},
// ...
})示例效果:
Vuex actions
actions 和 mutations 类似,都用于定义事件处理方法,但 actions 定义的方法,可以执行异步操作,actions 的本质还是调用 mutations。
示例:使用 actions 异步更新数据
仓库配置文件(src/store/index.js):
export default new Vuex.Store({
// ...
mutations: {
updateTitle(state, param) {
state.title = param
},
// ...
},
actions: {
// actions:用于处理异步操作
// context:Vuex.Store对象
// param:要修改的新数据,可以不传
asyncUpdateTitle(context, param) {
// actions本质上还是调用mutations
setTimeout(function () {
context.commit('updateTitle', param)
}, 3000)
}
}
})根组件(src/App.vue):
export default {
methods: {
changeTitle() {
// 通过this.$store.dispatch()调用actions中的更新数据的方法
// 参数1:调用的actions方法名
// 参数2:新的参数的值,可以不传
this.$store.dispatch('asyncUpdateTitle', 'Hello, duozai')
},
// ...
}
}示例效果:
Vuex 状态管理模式
在 Vue 中,组件的状态变化是通过单向数据流的设计理念实现的,单向数据流是指只能从一个方向修改状态。
单向数据流的三大关键要素:
- 状态(State):驱动应用的数据源。
- 视图(View):以声明方式将状态映射到视图。
- 操作(Actions):响应在视图上的用户输入导致的状态变化。
Vue 的单向数据流增强了组件之间的独立性,但是存在多个组件共享数据的时候,单向数据流就难以对数据进行维护。
Vuex 就是专门为 Vue 设计的状态(数据)管理库,利用 Vue 的细粒度数据响应机制来进行高效的状态更新。
Vuex 状态管理核心工作流程: