VUE3基础

可以向下兼容,也可写为vue3+vue2型

setup

setup参数

setup 函数包含了两个入参:

参数 类型 含义 是否必传
props object 由父组件传递下来的数据
context object 组件的执行上下文

props是响应式的
context 只是一个普通的对象(可以用ES解构),它暴露三个组件的 Property :

属性 类型 作用
attrs 非响应式对象 未在 Props 里定义的属性都将变成 Attrs
slots 非响应式对象 组件插槽,用于接收父组件传递进来的模板内容
emit 方法 触发父组件绑定下来的事件

defineComponent可用于简化对props和context的类型声明
用法为

1
2
3
4
5
6
7
8
9
10
11
12
import { defineComponent } from 'vue'

// 使用 `defineComponent` 包裹组件的内部逻辑
export default defineComponent({
setup(props, context) {
// ...

return {
// ...
}
},
})

定义数据

Ref

  • let str=1:死数据,无法响应
  • let str=ref({a:1}):响应数据
    • 获取数据通过str.value.a
    • 也可以通过str.value.a改数据
    • 也可以通过str.value增加数据(例如str.value.b=2
      1
      2
      // TypeScript 会推导 `msg.value` 是 `string` 类型
      const msg = ref('Hello World')

Reactive

  • let str=reactive({a:1})
    • 无需使用value
    • 只能写对象或者数组

      reactive定义的可以通过toRefs改为响应式

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
32
// 方式一:先定义接口再响应式
// 先声明对象的格式
interface Member {
id: number
name: string
}
// 在定义对象时指定该类型
const userInfo = ref<Member>({
id: 1,
name: 'Tom',
})

// 方式二:使用react
const userInfo: Member = reactive({ id: 1, name: 'Tom', })


//使用数组
const userList: Member[] = reactive([
{
id: 1,
name: 'Tom',
},
{
id: 2,
name: 'Petter',
},
{
id: 3,
name: 'Andy',
},
])

reactive在读取对象字段和修改的时候没有任何问题

但是在数组上需要注意

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
let uids: number[] = reactive([1, 2, 3])

/**
* 不推荐使用这种方式,会丢失响应性
* 异步添加数据后,模板不会响应更新
*/
uids = []

// 异步获取数据后,模板依然是空数组
setTimeout(() => {
uids.push(1)
}, 1000)

//--------------------------
const uids: number[] = reactive([1, 2, 3])

/**
* 推荐使用这种方式,不会破坏响应性
*/
uids.length = 0

// 异步获取数据后,模板可以正确的展示
setTimeout(() => {
uids.push(1)
}, 1000)

不要对reactive进行解构操作!否则失去响应性

1
2
3
const newUserInfo: Member = { ...userInfo }
const { name } = userInfo
//都无法实现响应

toRef

  1. 转换对象的某个属性
    如果想转换 name 这个字段为 Ref 变量,只需要这样操作:
1
2
3
4
const name = toRef(userInfo, 'name')
//第一个是 `reactive` 对象, 第二个是要转换的 `key`
console.log(name.value) // Petter

此后,修改 Ref 变量的值,reactive内部和ref两者同步更新

如果转换的属性是可选项(?)可以传入第三个参数代表默认值,在无值的时候会变以默认值代替

  1. 转换数组的某个元素
1
2
3
4
5
// 这一次声明的是数组
const words = reactive(['a', 'b', 'c'])

// 通过下标 `0` 转换第一个 item
const a = toRef(words, 0)

如果转换的那个下标不确定是否存在(可能越界了),可以传入第三个参数代表默认值,在无值的时候会变以默认值代替

toRefs

这里比toref多了个s

对对象和数组都适用

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
interface Member {
id: number
name: string
}

// 声明一个 Reactive 变量
const userInfo: Member = reactive({
id: 1,
name: 'Petter',
})

// 传给 `toRefs` 作为入参
const userInfoRefs = toRefs(userInfo)


//----------
//当然可以新定义接口来实现类型。当然这没必要

// 新声明的类型每个字段都是一个 Ref 变量的类型
interface MemberRefs {
id: Ref<number>
name: Ref<string>
}

// 使用新的类型进行声明
const userInfoRefs: MemberRefs = toRefs(userInfo)

转换后的 Reactive 对象或数组支持 ES6 的解构,并且不会失去响应性

1
2
3
4
5
6
7
8
// 为了提高开发效率,可以直接将 Ref 变量直接解构出来使用
const { name } = toRefs(userInfo)
console.log(name.value) // Petter

// 此时对解构出来的变量重新赋值,原来的变量也可以同步更新
name.value = 'Tom'
console.log(name.value) // Tom
console.log(userInfo.name) // Tom
Hooks 函数
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
32
33
34
35
36
37
38
import { reactive, toRefs } from 'vue'

// 声明 `useCalculator` 数据状态类型
interface CalculatorState {
// 这是要用来计算操作的数据
num: number
// 这是每次计算时要增加的幅度
step: number
}

// 声明一个 “使用计算器” 的函数
function useCalculator() {
// 通过数据状态中心的形式,集中管理内部变量
const state: CalculatorState = reactive({
num: 0,
step: 10,
})

// 功能函数也是通过数据中心变量去调用
function add() {
state.num += state.step
}

return {
...toRefs(state),
add,
}
}


// 解构出来的 `num` 和 `step` 都是 Ref 变量
const { num, step, add } = useCalculator()
console.log(num.value) // 0
console.log(step.value) // 10

// 调用计算器的方法,数据也是会得到响应式更新
add()
console.log(num.value) // 10

组件

  • 子组件接收

    • defineProps({ })
    • 或者在setup函数中
      • setup(props){ }:这里可以接收props
  • 父组件接收

1
2
3
4
5
6
7
8
// 子组件
let num ref(200);
const emit defineEmits<{
(e:'fn',id:number):void
}>()
const changeNum ()=>{
emit('fn',num)
}

生命周期

可以从下表直观地了解:

Vue 2 生命周期 Vue 3 生命周期 执行时间说明
beforeCreate setup 组件创建前执行
created setup 组件创建后执行
beforeMount onBeforeMount 组件挂载到节点上之前执行
mounted onMounted 组件挂载完成后执行
beforeUpdate onBeforeUpdate 组件更新之前执行
updated onUpdated 组件更新完成之后执行
beforeDestroy onBeforeUnmount 组件卸载之前执行
destroyed onUnmounted 组件卸载完成后执行
errorCaptured onErrorCaptured 当捕获一个来自子孙组件的异常时激活钩子函数

可以看到 Vue 2 生命周期里的 beforeCreatecreated ,在 Vue 3 里已被 setup 替代。

用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { defineComponent, onBeforeMount, onMounted } from 'vue'

export default defineComponent({
setup() {
console.log(1)

onBeforeMount(() => {
console.log(2)
})

onMounted(() => {
console.log(3)
})

console.log(4)
},
})
// 输出顺序为:1、4、2、3

简单的hello world

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 代码和 Vue 2 一样 -->
<template>
<p class="msg">{{ msg }}</p>
</template>

<!-- Script 代码需要使用 Vue 3 的新写法-->
<script lang="ts">
// Vue 3 的 API 需要导入才能使用
import { defineComponent } from 'vue'

// 使用 `defineComponent` 包裹组件代码
// 即可获得完善的 TypeScript 类型推导支持
export default defineComponent({
setup() {
// 在 `setup` 方法里声明变量
const msg = 'Hello World!'

// 将需要在 `<template />` 里使用的变量 `return` 出去
return {
msg,
}
},
})
</script>

<!-- CSS 代码和 Vue 2 一样 -->
<style scoped>
.msg {
font-size: 14px;
}
</style>

函数、侦听与计算

函数

在模板里通过 @click@change 等行为触发,和变量一样,需要把函数名在 setup 里进行 return 出去。

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
32
33
34
35
<template>
<p>{{ msg }}</p>

<!-- 在这里点击执行 `return` 出来的方法 -->
<button @click="updateMsg">修改MSG</button>
</template>

<script lang="ts">
import { defineComponent, onMounted, ref } from 'vue'

export default defineComponent({
setup() {
const msg = ref<string>('Hello World!')

// 这个要暴露给模板使用,必须 `return` 才可以使用
function updateMsg() {
msg.value = 'Hi World!'
}

// 这个要在页面载入时执行,无需 `return` 出去
const init = () => {
console.log('init')
}

onMounted(() => {
init()
})

return {
msg,
updateMsg,
}
},
})
</script>

侦听watch

1
2
3
4
5
6
7
8
import { watch } from 'vue'

// 一个用法走天下
watch(
source, // 必传,要侦听的数据源
callback // 必传,侦听到变化后要执行的回调函数
// options // 可选,一些侦听选项
)

options 是一个对象的形式传入,有以下几个选项:

选项 类型 默认值 可选值 作用
deep boolean false true \ false 是否进行深度侦听
immediate boolean false true \ false 是否立即执行侦听回调
flush string ‘pre’ ‘pre’ \ ‘post’ \ ‘sync’ 控制侦听回调的调用时机
onTrack (e) => void 在数据源被追踪时调用
onTrigger (e) => void 在侦听回调被触发时调用

如果是reactive,deep是强制开启的,无需手动开启,但是对于ref,如果侦听的是ref对象/数组,需要deep

如果侦听对象的某个属性,需要用类似getter的语法

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
32
33
34
35
36
export default defineComponent({
setup() {
// 定义一个响应式数据
const userInfo = reactive({
name: 'Petter',
age: 18,
})

// 2s后改变数据
setTimeout(() => {
userInfo.name = 'Tom'
}, 2000)

/**
* 可以直接侦听这个响应式对象
* callback 的参数如果不用可以不写
*/
watch(userInfo, () => {
console.log('侦听整个 userInfo ', userInfo.name)
})

/**
* 也可以侦听对象里面的某个值
* 此时数据源需要写成 getter 函数
*/
watch(
// 数据源,getter 形式
() => userInfo.name,
// 回调函数 callback
(newValue, oldValue) => {
console.log('只侦听 name 的变化 ', userInfo.name)
console.log('打印变化前后的值', { oldValue, newValue })
}
)
},
})

支持批量侦听(将watch的回调函数抽离)

1
2
3
4
5
6
7
8
9
10
11
// 抽离相同的处理行为为公共函数
const handleWatch = (
newValue: string | number,
oldValue: string | number
): void => {
console.log({ newValue, oldValue })
}

// 然后定义多个侦听操作,传入这个公共函数
watch(message, handleWatch)
watch(index, handleWatch)

数据源也可批量

1
2
3
4
5
6
7
8
9
watch(
// 数据源改成了数组
[message, index],
// 回调的入参也变成了数组,每个数组里面的顺序和数据源数组排序一致
([newMessage, newIndex], [oldMessage, oldIndex]) => {
console.log('message 的变化', { newMessage, oldMessage })
console.log('index 的变化', { newIndex, oldIndex })
}
)

watchEffect

简化版的watch,会响应式追踪其依赖

watcheffect不可访问变化前后的值
watcheffect默认执行一次,相当于开启了immediate

1
2
3
4
5
6
7
8
9
import { defineComponent, ref, watchEffect } from 'vue'
const getUserInfo = (): void => {
console.log({
name: name.value,
age: age.value,
})
}
// 直接侦听调用函数,在每个数据产生变化的时候,它都会自动执行
watchEffect(getUserInfo)

计算

1
2
3
4
5
6
// 定义基本的数据
const firstName = ref<string>('Bill')
const lastName = ref<string>('Gates')

// 定义需要计算拼接结果的数据
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
  1. 定义出来的 computed 变量,和 Ref 变量的用法一样,也是需要通过 .value 才能拿到它的值
  2. 但是区别在于,默认情况下 computedvalue 是只读的
  3. 一般情况下,是不需要显式的去定义 computed 出来的变量类型的。

注意:计算更新仅限于其以来的响应式更新

1
2
3
4
5
6
7
8
9
const nowTime = computed(() => new Date())
console.log(nowTime.value)
// 输出 Sun Nov 14 2021 21:07:00 GMT+0800 (GMT+08:00)

// 2s 后依然是跟上面一样的结果
setTimeout(() => {
console.log(nowTime.value)
// 还是输出 Sun Nov 14 2021 21:07:00 GMT+0800 (GMT+08:00)
}, 2000)

假设要获取当前的时间信息,因为不是响应式数据(new Date()不是响应式的),所以这种情况下就需要用普通的函数去获取返回值,才能拿到最新的时间。

setter

通过 computed 定义的变量默认都是只读的形式(只有一个 getter ),但是在必要的情况下,也可以使用其 setter 属性来更新数据。

1
2
3
4
5
6
7
8
9
10
11
// 注意这里computed接收的入参已经不再是函数
const foo = computed({
// 这里需要明确的返回一个值
get() {
// ...
},
// 这里接收一个参数,代表修改 foo 时,赋值下来的新值
set(newValue) {
// ...
},
})

举个例子——

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
// 还是这2个数据源
const firstName = ref<string>('Bill')
const lastName = ref<string>('Gates')

// 这里配合setter的需要,改成了另外一种写法
const fullName = computed({
// getter还是返回一个拼接起来的全名
get() {
return `${firstName.value} ${lastName.value}`
},
// setter这里改成只更新firstName,注意参数也定义TS类型
set(newFirstName: string) {
firstName.value = newFirstName
},
})
console.log(fullName.value) // 输出 Bill Gates

// 2s后更新一下数据
setTimeout(() => {
// 对fullName的赋值,其实更新的是firstName
fullName.value = 'Petter'

// 此时firstName已经得到了更新
console.log(firstName.value) // 会输出 Petter

// 当然,由于firstName变化了,所以fullName的getter也会得到更新
console.log(fullName.value) // 会输出 Petter Gates
}, 2000)

他是侦听set的变化!set中firstName变化了。

指令、插槽

自定义指令

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
32
<template>
<!-- 这个使用默认值 `unset` -->
<div v-highlight>{{ msg }}</div>

<!-- 这个使用传进去的黄色 -->
<div v-highlight="`yellow`">{{ msg }}</div>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue'

export default defineComponent({
// 自定义指令在这里编写,和 `setup` 同级别
directives: {
// `directives` 下的每个字段名就是指令名称
highlight: {
// 钩子函数
mounted(el, binding) {
el.style.backgroundColor =
typeof binding.value === 'string' ? binding.value : 'unset'
},
},
},
setup() {
const msg = ref<string>('Hello World!')

return {
msg,
}
},
})
</script>

插槽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//父组件
<template>
<Child>
<!-- 注意这里,子组件标签里面传入了 HTML 代码 -->
<p>这是插槽内容</p>
</Child>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import Child from '@cp/Child.vue'

export default defineComponent({
components: {
Child,
},
})
</script>

//子组件
<template>
<slot />
</template>

具名插槽

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
32
33
34
//父组件
<template>
<Child>
<!-- 传给标题插槽 -->
<template v-slot:title>
<h1>这是标题</h1>
</template>

<!-- 传给作者插槽 -->
<template v-slot:author>
<h1>这是作者信息</h1>
</template>

<!-- 传给默认插槽 -->
<p>这是插槽内容</p>
</Child>
</template>
//子组件
<template>
<!-- 显示标题的插槽内容 -->
<div class="title">
<slot name="title" />
</div>

<!-- 显示作者的插槽内容 -->
<div class="author">
<slot name="author" />
</div>

<!-- 其他插槽内容放到这里 -->
<div class="content">
<slot />
</div>
</template>

子组件如果不指定默认插槽,那么在具名插槽之外的内容将不会被渲染。

其他

返回两个相同的变量名

下面这种情况,会以单独的 name 为渲染数据:

哪个会生效,取决于谁排在后面,因为 return 出去的其实是一个对象,在对象里,如果存在相同的 key ,则后面的会覆盖前面的。

1
2
3
4
return {
...userInfoRefs,
name,
}

路由

需要注意的是,与 Vue 3 配套的路由版本是 vue-router 4.x 以上才可以正确适配项目。

路由树

index.ts(代码中标注了*的才是必选)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/router/routes.ts

import { createRouter, createWebHistory } from 'vue-router'
// 基础1*使用 TypeScript 时需要导入路由项目的类型声明
import type { RouteRecordRaw } from 'vue-router'
// 基础2*使用路由项目类型声明一个路由数组
const routes: Array<RouteRecordRaw> = [
// ...
]

const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
})

// 基础3*将路由数组导出给其他模块使用
export default router
  • 公共基础路径:项目配置中的base
  • 一级路由

    1
    2
    3
    4
    5
    6
    7
    8
    import Home from '@views/home.vue'
    const routes: Array<RouteRecordRaw> = [
    {
    path: '/',
    name: 'home',
    component: Home,//可以实现 路由懒加载 。
    },//访问路径https://example.com/home
    ]
  • 多级路由:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    const routes: Array<RouteRecordRaw> = [
    // 注意:这里是一级路由
    {
    path: '/lv1',
    name: 'lv1',
    component: () => import('@views/lv1.vue'),
    // 注意:这里是二级路由,在 `path` 的前面没有 `/`
    children: [
    {
    path: 'lv2',
    name: 'lv2',
    component: () => import('@views/lv2.vue'),
    // 注意:这里是三级路由,在 `path` 的前面没有 `/`
    children: [
    {
    path: 'lv3',
    name: 'lv3',
    component: () => import('@views/lv3.vue'),
    },//访问地址:https://example.com/lv1/lv2/lv3
    ],
    },
    ],
    },
    ]

路由渲染

所有路由组件,要在访问后进行渲染,都必须在父级组件里带有 <router-view /> 标签。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<!-- 登录 -->
<Login v-if="route.name === 'login'" />

<!-- 注册 -->
<Register v-else-if="route.name === 'register'" />

<!-- 带有侧边栏的其他路由 -->
<div v-else>
<!-- 固定在左侧的侧边栏 -->
<Sidebar />

<!-- 路由 -->
<router-view />
</div>
</template>

使用 route

获取路由信息

先导入路由 API :

1
import { useRoute } from 'vue-router'

再在 setup 里定义一个变量来获取当前路由:

1
const route = useRoute()

接下来就可以通过定义好的变量 route 去获取当前路由信息了。

如果要在 <template /> 里使用路由,记得把 routesetup 里 return 出去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 获取路由名称
console.log(route.name)

// 获取路由参数
console.log(route.params.id)

// 做面包削功能
// 获取路由记录
const matched = route.matched

// 获取该记录的路由个数
const max = matched.length

// 获取倒数第二个路由(也就是当前路由的父级路由)
const parentRoute = matched[max - 2]
//如果有配置父级路由,那么 `parentRoute` 就是父级路由信息,否则会返回 `undefined` 。

操作路由

1
2
3
4
5
6
7
// 跳转首页
router.push({
name: 'home',
})

// 返回上一页
router.back()

router-link 是一个全局组件,可直接在 <template /> 里直接使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  <!-- 不带参数 -->
<template>
<router-link to="/home">首页</router-link>
</template>
<!-- 带参数 -->
<router-link
class="link"
:to="{
name: 'article',
params: {
id: 123,
},
}"
>
这是文章的标题
</router-link>

默认会生成<a>标签,如果希望转化为其他标签,可以使用(如下图为转化为到span)

1
2
3
<router-link to="/home" custom v-slot="{ navigate }">
<span class="link" @click="navigate"> 首页 </span>
</router-link>

参数解释——

  1. custom ,一个布尔值,用于控制是否需要渲染为 a 标签,当不包含 custom 或者把 custom 设置为 false 时,则依然使用 a 标签渲染。

  2. v-slot 是一个对象,用来决定标签的行为,它包含了:

字段 含义
href 解析后的 URL,将会作为一个 a 元素的 href 属性
route 解析后的规范化的地址
navigate 触发导航的函数,会在必要时自动阻止事件,和 router-link 同理
isActive 如果需要应用激活的 class 则为 true,允许应用一个任意的 class
isExactActive 如果需要应用精确激活的 class 则为 true,允许应用一个任意的 class

一般来说,v-slot 必备的只有 navigate ,用来绑定元素的点击事件,否则元素点击后不会有任何反应,其他的可以根据实际需求来添加。

路由元信息

在定义路由树的时候可以配置 meta 字段,比如下面就是包含了多种元信息的一个登录路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const routes: Array<RouteRecordRaw> = [
{
path: '/login',
name: 'login',
component: () => import('@views/login.vue'),
meta: {
title: '登录',
isDisableBreadcrumbLink: true,
isShowBreadcrumb: false,
addToSidebar: false,
sidebarIcon: '',
sidebarIconAlt: '',
isNoLogin: true,
},
},
]

可以配合路游拦截

导航守卫

路由里的全局钩子

在创建 router 的时候进行全局的配置,也就是说,只要配置了钩子,那么所有的路由在被访问到的时候,都会触发这些钩子函数。(一般在APP.vue或者全局的header.vue使用)

可用钩子 含义 触发时机
beforeEach 全局前置守卫 在路由跳转前触发
beforeResolve 全局解析守卫 在导航被确认前,同时在组件内守卫和异步路由组件被解析后
afterEach 全局后置守卫 在路由跳转完成后触发
  • beforeEach:

    • to: 即将今日的对象
    • from:来自哪里
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      //设置标题
      router.beforeEach((to, from) => {
      const { title } = to.meta
      document.title = title || '默认标题'
      })

      //是否登录
      router.beforeEach((to, from) => {
      const { isNoLogin } = to.meta
      if (!isNoLogin) return '/login'
      })

      // params丢失的时候返回首页
      router.beforeEach((to) => {
      if (to.name === 'article' && to.matched.length === 0) {
      return '/'
      }
      })
  • beforeResolve,与befroeEach相同。

    • 它通常会用在一些申请权限的环节,比如一些 H5 页面需要申请系统相机权限、一些微信活动需要申请微信的登录信息授权,获得权限之后才允许获取接口数据和给用户更多的操作,使用 beforeEach 时机太早,使用 afterEach 又有点晚,那么这个钩子的时机就刚刚好。
  • afterEach
    • 参数同befroeEach
    • 如流量统计

在组件内使用全局钩子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { defineComponent } from 'vue'
import { useRouter } from 'vue-router'

export default defineComponent({
setup() {
// 定义路由
const router = useRouter()

// 调用全局钩子
router.beforeEach((to, from) => {
// ...
})
},
})

路由里的独享钩子

如果只是有个别路由要做处理,可以使用路由独享的守卫
写在router的index.ts里面

1
2
3
4
5
6
7
8
9
10
11
const routes: Array<RouteRecordRaw> = [
{
path: '/home',
name: 'home',
component: () => import('@views/home.vue'),
// 在这里添加单独的路由守卫
beforeEnter: (to, from) => {
document.title = '程沛权 - 养了三只猫'
},
},
]

顺序:beforeEach(全局) > beforeEnter(独享) > beforeResolve(全局)。

重定向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 路由index.ts文件
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'home',
component: () => import('@views/home.vue'),
meta: {
title: 'Home',
},
},
// 访问这个路由会被重定向到首页
{
path: '/error',
redirect: '/',
},
]

redirect 字段可以接收三种类型的值:

类型 填写的值
string 另外一个路由的 path
route 另外一个路由(类似 router.push
function 可以判断不同情况的重定向目标,最终 return 一个 path 或者 route
  • 使用string:缺点也显而易见,只能针对那些不带参数的路由。
  • route:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    const routes: Array<RouteRecordRaw> = [
    // 重定向到 `/home` ,并带上一个 `query` 参数
    {
    path: '/',
    redirect: {
    name: 'home',
    query: {
    from: 'redirect',
    },
    },
    },
    // 真正的首页
    {
    path: '/home',
    name: 'home',
    component: () => import('@views/home.vue'),
    },
    ]//最终访问的地址就是 `https://example.com/home?from=redirect`
  • 配置为function

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
const routes: Array<RouteRecordRaw> = [
// 访问主域名时,根据用户的登录信息,重定向到不同的页面
{
path: '/',
redirect: () => {
// `loginInfo` 是当前用户的登录信息
// 可以从 `localStorage` 或者 `Pinia` 读取
const { groupId } = loginInfo

// 根据组别 ID 进行跳转
switch (groupId) {
// 管理员跳去仪表盘
case 1:
return '/dashboard'

// 普通用户跳去首页
case 2:
return '/home'

// 其他都认为未登录,跳去登录页
default:
return '/login'
}
},
},
]

路由别名

添加一个 alias 字段即可轻松实现:

1
2
3
4
5
6
7
8
9
const routes: Array<RouteRecordRaw> = [
{
path: '/home',
alias: '/index',
name: 'home',
component: () => import('@views/home.vue'),
},
]
//如上的配置,即可实现可以通过 `/home` 访问首页,也可以通过 `/index` 访问首页。

404

配置方法

1
2
3
4
5
6
7
const routes: Array<RouteRecordRaw> = [
{
path: '/:pathMatch(.*)*',
name: '404',
component: () => import('@views/404.vue'),
},
]

这样配置之后,只要访问到不存在的路由,就会显示为这个 404 模板。

新版的路由不再支持直接配置通配符 * ,而是必须使用带有自定义正则表达式的参数进行定义,详见官网 删除了 *(星标或通配符)路由 的说明。

路由侦听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { defineComponent, watchEffect } from 'vue'
import { useRoute } from 'vue-router'

export default defineComponent({
setup() {
const route = useRoute()

// 从接口查询文章详情
async function queryArticleDetail() {
const id = Number(route.params.id) || 0
console.log('文章 ID 是:', id)

const res = await axios({
url: `/article/${id}`,
})
// ...
}

// 直接侦听包含路由参数的那个函数
watchEffect(queryArticleDetail)
},
})