初识vue(下)

路由

一对一的映射关系

vue中的路由: 路径与组件之间的映射关系

SPA

Single Page Application,单页面应用(SPA): 所有功能在 一个html页面 上实现

开发分类 实现方式 页面性能 开发效率 用户体验 学习成本 首屏加载 SEO
单页 一个html页面 按需更新,性能高 非常好
多页 多个html页面 整页更新,性能低 中等 一般 中等

优点:按需更新性能高,开发效率高,用户体验好

缺点:学习成本,首屏加载慢,不利于SEO

应用场景:系统类网站 / 内部网站 / 文档类网站 /移动端站点

vue中的路由插件vuerouter

作用:修改访问地址栏路径时,切换显示匹配的组件

VueRouter的基本使用步骤:

1.下载

image-20230831202839981.png

2.引入

image-20230831202858913.png

3.安装注册

image-20230831202910254.png

4.创建路由对象

image-20230831202922922.png

5.注入,将路由对象注入到new Vue实例中,建立关联

image-20230831202931311.png

两个核心步骤

  1. 创建需要的组件(views)目录,配置路由规则
  2. 封装抽离路由模块,好处:拆分模块:利于维护
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// @ 表示 src 的绝对路径
import Find from '@/views/Find'
import My from '@/views/My'
import Friend from '@/views/Friend'

import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)

// 创建了一个路由对象
// 传入配置对象,配置路由规则
// 路由配置规则: 路径和组件之间的一一对应的映射关系
const router = new VueRouter({
// 路由规则是多条,所以 routers 是复数形式
routes: [
// 一个对象对应一条规则
// path 用户访问的地址
// components 用户访问对应地址其对应要展示的组件内容
{ path: '/find', component: Find },
{ path: '/my', component: My },
{ path: '/friend', component: Friend },
]
})
//对外暴露路由对象
export default router
  1. 配置导航,配置路由出口(路径匹配的组件显示的位置)
1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div>
<div class="footer_wrap">
<a href="#/find">发现音乐</a>
<a href="#/my">我的音乐</a>
<a href="#/friend">朋友</a>
</div>
<div class="top">
<!-- 路由出口 → 匹配的组件所展示的位置 -->
<router-view></router-view>
</div>
</div>
</template>

组件的分类

页面组件 - views文件夹 => 配合路由,页面展示

复用组件 - components文件夹 => 封装复用

声明式导航

导航高亮

vue-router 提供了一个全局组件 router-link (取代 a 标签)

① 能跳转,配置 to 属性指定路径(必须) 。本质还是 a 标签 ,使用to 无需 #

#为锚点实现在当前页面跳转无需刷新页面

② 能高亮,默认就会提供高亮类名,可以直接设置高亮样式

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div>
<div class="footer_wrap">
<router-link to="/find">发现音乐</router-link>
<router-link to="/my">我的音乐</router-link>
<router-link to="/friend">朋友</router-link>
</div>
<div class="top">
<!-- 路由出口 → 匹配的组件所展示的位置 -->
<router-view></router-view>
</div>
</div>
</template>

当前选中的router-link 的两个类名

image-20230831213601560.png

① router-link-active 模糊匹配 (用的多)

to=”/my” 可以匹配 /my /my/a /my/b ….

② router-link-exact-active 精确匹配

to=”/my” 仅可以匹配 /my

image.png

声明式导航 —跳转传参

  1. 查询参数传参

① 语法格式如下

to=”/path?参数名=值”

② 对应页面组件接收传递过来的值

$route.query.参数名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div class="home">
<div class="logo-box"></div>
<div class="search-box">
<input type="text">
<button>搜索一下</button>
</div>
<div class="hot-link">
热门搜索:
<!-- query 传参:查询字符串传参 -->
<!--1.传递: 路径?参数名1 = 参数值1 & 参数名2 = 参数值2 -->
<!--2.去对应的目标组件中接收参数 $route.query.参数名 -->
<router-link to="/search?keywords=今天下大雨了">今天下大雨了</router-link>
<router-link to="/search?keywords=小白,如何月薪过万?">小白,如何月薪过万</router-link>
</div>
</div>
</template>

动态路由传参

①去 router/index.js 文件下配置动态路由

image.png

用户点击访问的路径后面添加上 :参数名 就是动态路由的参数

② 配置导航链接

传递: to=”/path/参数值”

③ 对应页面组件接收传递过来的值

接收 : $route.params.参数名

动态路由参数可选符

? 表示可选参数,传不传参数都可以匹配到这条路由规则

image.png

路由重定向

**问题:**网页打开, url 默认是 / 路径,未匹配到组件时,会出现空白

语法: { path: 匹配路径, redirect: 重定向到的路径 },

1
2
3
4
5
6
7
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/home'},
{ path: '/home', component: Home },
{ path: '/search', component: Search }
]
})

路由懒加载

当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就会更加高效。

为什么要使用路由懒加载?

为给客户更好的客户体验,首屏组件加载速度更快一些,解决白屏问题。

定义

懒加载简单来说就是延迟加载或按需加载,即在需要的时候的时候进行加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// import Home from '@/views/Home.vue'
// 替换成:() => import('@/views/Home.vue') 直接作为component选项
const routes = [
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue')
},
{
path: '/about',
name: 'About',
component: () => import('@/views/About.vue')
},
];

const router = new VueRouter({
routes
});

每个路由的component选项使用了import()函数,它会返回一个 Promise,用于异步加载对应的路由组件。这样,在初始加载页面时,只会下载首页所需的Home.vue组件,而不会加载其他路由组件。当用户访问 About 页面时,才会异步加载并渲染About.vue组件

编程式导航

编程式导航:使用js代码来实现跳转

语法:

path 路径跳转 (简易方便)

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
<div class="home">
<div class="logo-box"></div>
<div class="search-box">
<input type="text">
<button @click="goSearch">搜索一下</button>
</div>
<div class="hot-link">
热门搜索:
<router-link to="/search">今天早餐你喝胡辣汤了吗?</router-link>
<router-link to="/search">小白如何月薪过万?</router-link>
</div>
</div>
</template>

<script>
export default {
name: 'FindMusic',
methods: {
goSearch() {
this.$router.push('/search') //简单写法
// this.$router.push({ path: '/search'}) //完整写法
}
}
}
</script>

path路径跳转传参

  1. query传参

image.png
2. 动态路由传参

image.png

② name 命名路由跳转 (适合 path 路径长的场景)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<template>
<div class="home">
<div class="logo-box"></div>
<div class="search-box">
<input type="text">
<button @click="goSearch">搜索一下</button>
</div>
<div class="hot-link">
热门搜索:
<router-link to="/search">今天早餐你喝胡辣汤了吗?</router-link>
<router-link to="/search">小白如何月薪过万?</router-link>
</div>
</div>
</template>

<script>
export default {
name: 'FindMusic',
methods: {
goSearch() {
// path 路径跳转
// this.$router.push('/search') //简单写法
// // this.$router.push({ path: '/search'}) //完整写法

// name 属性跳转
// 语法: this.$router.push( {name : '路由名'} )
this.$router.push( {name : 'search'} )
}
}
}
</script>

name属性跳转传参

  1. query传参

image.png

  1. 动态路由传参
    image.png

导航方式和传参方式有点多,贴一张思维导图便于理解和查找

路由导航.png

组件缓存

是 Vue 的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。 是一个抽象组件,它自身不会渲染成一个 DOM 元素,也不会出现在父组件链中

此外, 要求被切换到的组件都有自己的名字,不论是通过组件的 name 选项还是局部 / 全局注册

优点:

在组件切换过程中把切换出去的组件保留在内存中,防止重复渲染 DOM,减少加载时间及性能消耗,提高用户体验性

的三个属性:

  1. include:组件名数组,只有匹配的组件会被缓存

  2. exclude:组件名数组,任何匹配的组件都不会被缓存,二者都可以用逗号分隔的字符串、正则表达式或一个数组来表示

  3. max:数字,最多可以缓存多少组件实例

includeexclude只会设置其中一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 逗号分隔字符串 -->
<keep-alive include="a,b">
<component :is="view"></component>
</keep-alive>

<!-- 正则表达式 (使用 v-bind) -->
<keep-alive :include="/a|b/">
<component :is="view"></component>
</keep-alive>

<!-- 数组 (使用 v-bind) -->
<keep-alive :include="['a', 'b']">
<component :is="view"></component>
</keep-alive>
1
2
3
4
<keep-alive :max="10">
<component :is="view"></component>
</keep-alive>

的使用会触发两个生命周期函数

activated:当组件被激活(使用)的时候触发(进入这个页面的时候触发)

1
2
3
activated () {
console.log('activated 激活 → 进入页面');
},

deactivated:当组件不被使用的时候触发(离开这个页面的时候触发)

1
2
3
deactivated() {
console.log('deactivated 失活 → 离开页面');
}

组件缓存后就不会执行组件的 createdmounteddestroyed 等钩子了。所以其提供了 activated (组件激活)和 deactivated (组件失活)钩子,帮我们实现业务需求

导航守卫(路由守卫)

正如其名,vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。这里有很多方式植入路由导航中:全局的,单个路由独享的,或者组件级的

路由守卫有三种:

全局前置守卫: beforeEach、全局后置钩子:afterEach

所谓全局是指路由实例上直接操作的钩子函数,他的特点是所有路由配置的组件都会触发,直白点就是触发路由就会触发这些钩子函数

独享守卫: beforeEnterbeforeLeave
组件内守卫:beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave
全局前置守卫

使用 router.beforeEach 注册一个全局前置守卫:

1
2
3
4
5
6
7
const router = createRouter({ ... })

router.beforeEach((to, from, next) => {
// ...
// 返回 false 以取消导航
return false
})

当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于等待中

守卫有三个参数:

to 往哪里去, 到哪去的路由信息对象
from 从哪里来, 从哪来的路由信息对象
next()是否放行(可选)
如果next()调用,就是放行
next(路径) 拦截到某个路径页面
可以返回的值如下:

false: 取消当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址
一个路由地址:通过一个路由地址跳转到一个不同的地址,就像你调用 router.push() 一样,你可以设置诸如 replace: truename: 'home' 之类的配置。当前的导航被中断,然后进行一个新的导航,就和 from 一样
如果遇到了意料之外的情况,可能会抛出一个 Error。这会取消导航并且调用 router.onError() 注册过的回调

如果什么都没有,undefined 或返回 true,则导航是有效的,并调用下一个导航守卫

全局后置钩子

全局后置钩子和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身

1
2
3
router.afterEach((to, from) => {
sendToAnalytics(to.fullPath)
})

它们对于分析、更改页面标题、声明页面等辅助功能以及许多其他事情都很有用

独享守卫

单路由独享守卫,独享守卫只有前置没有后置

1
2
3
4
5
6
7
8
9
10
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from, next) => {
// reject the navigation
return false
},
},
]

组件内守卫

你可以为路由组件添加以下配置:

beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const UserDetails = {
template: ...,
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被验证前调用
// 不能获取组件实例 `this` !
// 因为当守卫执行时,组件实例还没被创建!
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
// 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
},
beforeRouteLeave(to, from, next) {
// 在导航离开渲染该组件的对应路由时调用
// 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
},
}

Vuex

概念

Vuex 是一个专门为 Vue.js 应用程序开发的状态管理模式。**(可以理解为状态就是数据)**它通过提供一个集中存储管理应用的所有组件的状态,并提供了一些操作和方法来修改和访问这些状态。通过使用 Vuex,可以更好地组织和管理 Vue.js 应用程序的状态,提高代码的可维护性和可测试性。它适用于中大型的应用程序或需要共享状态的多个组件之间进行通信的场景

一般情况下,只有多个组件均需要共享的数据 ,才有必要存储在 Vuex 中,对于某个组件中的私有数据,依旧存储在组件自身的 data

使用 Vuex 优势主要有三点:
共同维护一份数据,数据集中化管理
响应式变化
操作简洁 (Vuex 提供了一些辅助函数)

Vuex使用

安装 Vuex 插件,初始化一个空仓库

安装 vuex,与 vue-router 类似,vuex 是一个独立存在的插件,如果脚手架初始化没有选 vuex,就需要额外安装。

1
yarn add vuex@3

新建 store/index.js 专门存放 vuex。为了维护项目目录的整洁,在 src 目录下新建一个 store 目录,其下放置一个index.js文件 (和 router/index.js 类似)

创建仓库 store/index.js

1
2
3
4
5
6
7
8
9
10
11
// 导入 vue
import Vue from 'vue'
// 导入 vuex
import Vuex from 'vuex'
// vuex 也是 vue 的插件, 需要 use 一下, 进行插件的安装初始化
Vue.use(Vuex)

// 创建仓库 store
const store = new Vuex.Store()
// 导出仓库
export default store

在 main.js 中导入挂载到 Vue 实例上

1
2
3
4
5
6
7
8
9
10
import Vue from 'vue'
import App from './App.vue'
import store from './store'

Vue.config.productionTip = false

new Vue({
render: h => h(App),
store
}).$mount('#app')

此刻起, 就成功创建了一个 空仓库!!

Vuex 的核心概念

Vuex 的核心概念包括:**State(状态)、Mutation(变化)、Action(动作)、Getter(获取器)、Module**(模块)

States

State(状态): Vuex 使用单一状态树来管理应用的状态,也就是一个包含了所有数据的对象。通过将数据放在一个集中的地方,可以方便地跟踪和管理应用的数据

State 提供唯一的公共数据源,所有共享的数据都要统一放到 Store 中的 State 中存储。在 state 对象中可以添加我们要共享的数据:

1
2
3
4
5
6
7
8
// 创建仓库 store
const store = new Vuex.Store({
// state 状态, 即数据, 类似于 vue 组件中的 data,
// 区别在于 data 是组件自己的数据, 而 state 中的数据整个 vue 项目的组件都能访问到
state: {
count: 10
}
})

访问State数据

通过仓库直接访问

组件中可以使用 this.$store 获取到 vuex 中的 store 对象实例,可通过 state 属性属性获取 count

state的数据---{{ $store.state.count }}

在非组件内,先引入当前的 store 对象,使用 store.state.count(属性名)来获取

或者将 state 属性定义在计算属性中:

1
2
3
4
5
6
// 把 state 中数据,定义在组件内的计算属性中
computed: {
count () {
return this.$store.state.count
}
}

使用计算属性简化了代码,但是每次都需要依次提供计算属性,比较繁琐,vuex 辅助函数帮我们解决了这个问题

辅助函数 mapState

mapState 是辅助函数,帮助我们把 store 中的数据映射到组件的计算属性中
使用步骤:

导入 mapState ( mapStatevuex 中的一个函数 )

import { mapState } from 'vuex'

采用数组形式引入 state 属性

mapState(['count']) 

利用展开运算符将导出的状态映射给计算属性

1
2
3
computed: {
...mapState(['count'])
}

mutations

Mutation(变化): 变化是修改数据的唯一方式。它类似于事件,每个变化都有一个类型和一个处理函数。通过提交一个变化,可以触发状态的修改

事实上确实是可以直接修改 store 中的数据,无需通过 mutation,并且不会报错。但直接修改可能会造成很严重的后果, Vue 官方也不希望我们这么做,如若担心自己可能无意间直接修改了 store 中的数据,可以开启严格模式 strict: true ,让 Vue 来监督我们:

1
2
3
4
5
6
7
8
9
10
const store  = new Vuex.Store({
strict: true
state: {
count: 0
},
// 定义mutations
mutations: {
}
// ...
})

访问mutations

state 数据的修改只能通过 mutations,并且 mutations 必须是同步的。mutations 是一个对象,对象中存放修改 state 的方法

1
2
3
4
5
6
7
mutations: {
// 第一个参数是当前 store 的 state 属性,也就是 vuex 里面存储的数据
// payload载荷 运输参数,调用mutaiions时,可以传递参数,传递载荷
addCount (state) {
state.count += 1
}
}

组件中提交 mutations(其实就是触发对应函数的执行)

this.$store.commit('addCount')

带参数的mutations

提交 mutations 是可以传递参数的 this.$store.commit(states, 参数)

提供 mutations 函数

1
2
3
4
5
6
mutations: {
...
addCount (state, count) {
state.count = count
}
},

提交 mutations

1
2
3
handle ( ) {
this.$store.commit('addCount', 10)
}

在调用触发函数执行的时候只能传递两个参数,1. states, 2.携带的数据 ,如需传递多条数据使用数组或是对象进行传递this.$store.commit('addCount',{***}/[***])
辅助函数

辅助函数mapMutations

mapMutations 和 mapState 很像

mutations 里面存的是一个函数,它是把位于 mutations 中的方法在methods中展开

而 mapState 是数据,映射的数据是在计算属性computed中展开

1
2
3
4
import  { mapMutations } from 'vuex'
methods: {
...mapMutations(['addCount'])
}

此时,就可以直接通过 this.addCount()调用

<button @click="addCount">+1</button>

mutations 中不能写异步代码,如果有异步的 ajax 请求,应该放置在 actions 中

actions

Action(动作): action 类似于变化,它可以包含任意异步操作。它们是通过提交一个 action 来触发的,然后在 actions 中可以执行异步操作并通过提交变化来修改状态

state 用于存放数据,mutations 用于同步更新数据(便于监测数据的变化,更新视图等,方便于调试工具查看变化),actions 则负责进行异步操作

定义actions

actions 的函数第一个参数是 context (上下文对象,参数名可修改),可以理解为是一个简化版本的 store 对象,它里面也有commit方法,触发mutation的执行,第二个参数同 mutations 一样,只能接受一个参数,多个参数用对象包裹后传递

1
2
3
4
5
6
7
8
actions: {
setAsyncCount (context, num) {
// 一秒后, 给一个数, 去修改 num
setTimeout(() => {
context.commit('changeCount', num)
}, 1000)
}
},

调用 actions 来进行异步修改 count

语法: this.$store.dispatch(参数一:context ,参数二:要进行修改的参数)

commit调用的是mutation函数

dispatch调用的是actions函数

1
2
3
setAsyncCount () {
this.$store.dispatch('setAsyncCount', 666)
}
辅助函数mapActions

actions 也有辅助函数(mapActions),可以将 actions 导入到组件中

1
2
3
4
import { mapActions } from 'vuex'
methods: {
...mapActions(['setAsyncCount'])
}

直接通过 this.方法就可以调用

<button @click="setAsyncCount(200)">异步修改数值</button>

getters

Getter(获取器): 获取器用于从状态中派生出新的状态。它可以将现有的状态进行计算和转换,并提供一个类似于计算属性的方式来访问这些派生状态

例如,state 中定义了 list 数组,而在组件中,需要显示所有大于5的数据,正常的方式,是需要 list 在组件中进行再一步的处理,但是 getters 可以帮助我们实现它:

1
2
3
4
5
6
7
8
state: {
list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}
getters: {
// getters函数的第一个参数是 state
// 必须要有返回值
filterList: state => state.list.filter(item => item > 5)
}

原始方式$store

1
2
3
4
5
6
7
8
9
<div>{{ $store.getters.filterList }}</div>

Getter 辅助函数与上文类似,均是用于用于获取对象属性值的函数,简化了代码

mapGetters

computed: {
...mapGetters(['filterList'])
}

使用

{{ filterList }}

modules

概念

Module(模块): 模块可以将整个状态树分割成更小的模块,每个模块都有自己的状态(state)、变化(mutation)、动作(action) 和获取器(getter)。这样可以将复杂的应用程序拆分成可维护和可测试的模块。通过模块化的方式,可以更好地组织和维护大型的状态管理

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿,于是就有了 Vuex 的模块化。模块化是使用 Vuex 的一种重要的模式,开发中基本上都是基于模块化思想来开发