Vue3+Ts

创建vue3项目

不适用vue-cli了,直接上vite

npm create vue@latest

然后选择一些配置项就行了,ts就ts呗

Vue3核心语法

setup

setup是vue3中一个新的配置项,值是一个函数,它是Composition Api表演的舞台,组件中所用到的:数据,方法,计算属性,监视等等,均配置在setup中

setup中无法使用this,是undefined


想要只是用一个script,但又想自定义组件名,可以使用这个插件

1
npm i vite-plugin-vue-setup-extend -D

然后在vite.config.ts中引入

1
2
3
4
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
plugins: [
VueSetupExtend(),
],

setup基本使用

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
39
40
41
42
43
44
45
46
47
48
49
50
<template>
<div class="person">
<h2>姓名:{{ name }}</h2>
<h2>年龄:{{ age }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="showTel">查看联系方式</button>
</div>
</template>

<!-- 这个script就是用来定义组件名了 -->
<!-- <script lang="ts">
export default {
name: 'Person'
}
</script> -->

<!-- 以后vue的文件中会有2个script标签 -->
<!-- 但是也可以安装插件实现 -->
<script lang="ts" setup name="Person1">
// 数据
let name = '张三' // 这样声明的变量不是响应式的,后面数据变化,页面并不会更改
let age = 18
let tel = '12321312'

// 方法
function changeName() {
name = 'zhangsan'
}

function changeAge() {
age += 1
}

function showTel() {
alert(tel)
}
</script>

<style scoped>
.person {
background-color: blue;
box-shadow: 0 0 10px;
padding: 20px;
}

button {
margin: 0 5px;
}
</style>

ref

使用ref创建基本类型的响应式数据

上面写的数据都不是响应式的,更改了数据,界面也不会变化,使用ref可以创建响应式的数据

使用时,要注意:

  • 插值语法中不写.value
  • 函数中使用必须写.value
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { ref } from 'vue'

// 数据
let name = ref('张三')
let age = ref(18)

// 方法
function changeName() {
name.value = 'zhangsan'
}

function changeAge() {
age.value += 1
}

ref定义引用类型的数据

实际上ref中的value还是Proxy,也就是说还是ractive

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<template>
<div class="person">
<h2>一辆{{ car.brand }}值{{ car.price }}</h2>
<button @click="changePrice">修改汽车价格</button>
<hr>
<h2>游戏列表</h2>
<ul>
<li v-for="g in games" :key="g.id">{{ g.name }}</li>
</ul>
<button @click="changeFirstGame">修改第一个游戏</button>
<hr>
<h2>{{ obj.a }}</h2>
</div>
</template>

<script lang="ts" setup name="Person">

import { ref,reactive } from 'vue'

// 数据
let car = ref({
brand: '奔驰',
price: 100
})
let games = ref([
{ id: 'dasfs1', name: '元神' },
{ id: 'dasfs2', name: '源神' },
{ id: 'dasfs3', name: '猿神' }
])

let obj = reactive({
a: 999
})

// ref和reactive都可以将对象设置为响应式,但实现方式不一样
console.log(car) // RefImpl {__v_isShallow: false, dep: undefined, __v_isRef: true, _rawValue: {…}, _value: Proxy(Object)}
console.log(obj) // Proxy(Object) {a: 999}

function changePrice() {
car.value.price += 10
console.log(car.value.price)
}

function changeFirstGame() {
games.value[0].name = '原神'
console.log(games)
}
</script>

<style scoped>
.person {
background-color: blue;
box-shadow: 0 0 10px;
padding: 20px;
}

button {
margin: 0 5px;
}
</style>

reactive

使用ref创建引用数据类型的响应式数据

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
39
40
41
42
43
44
45
46
47
48
49
50
51
<template>
<div class="person">
<h2>一辆{{ car.brand }}值{{ car.price }}</h2>
<button @click="changePrice">修改汽车价格</button>
<hr>
<h2>游戏列表</h2>
<ul>
<li v-for="g in games" :key="g.id">{{ g.name }}</li>
</ul>
<button @click="changeFirstGame">修改第一个游戏</button>
</div>
</template>

<script lang="ts" setup name="Person">

import { reactive } from 'vue'

// 数据
let car = reactive({
brand: '奔驰',
price: 100
})
// reactive 是深层次的,无论嵌套多少层
let games = reactive([
{ id: 'dasfs1', name: '元神' },
{ id: 'dasfs2', name: '源神' },
{ id: 'dasfs3', name: '猿神' }
])

function changePrice() {
car.price += 10
console.log(car.price)
}

function changeFirstGame() {
games[0].name = '原神'
console.log(games)
}
</script>

<style scoped>
.person {
background-color: blue;
box-shadow: 0 0 10px;
padding: 20px;
}

button {
margin: 0 5px;
}
</style>

ref对比reactive

  1. ref用来定义:基本数据类型,引用数据类型
  2. reactive用来定义:引用数据类型

区别

  1. ref创建的变量必须使用.value(可以使用volar创建自动添加.value)
  2. reactive重新分配一个新的对象,会失去响应式(可以使用Object.assign去整体替换)

使用原则

  1. 若需要一个基本类型的响应式数据,必须使用ref
  2. 若需要一个响应式对象,层级不深,ref,reactive都可以
  3. 若需要一个响应式对象,且层级较深,推荐使用reactive

**注意Object.assign(car,{ brand: ‘bwm’, price: 1 })**的用法

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<template>
<div class="person">
<h2>一辆{{ car.brand }}值{{ car.price }}</h2>
<button @click="changePrice">修改汽车价格</button>
<button @click="changeCar">修改汽车</button>
<hr>
<h2>当前求和为:{{ sum }}</h2>
<button @click="changeSum">点我sum+1</button>
</div>
</template>

<script lang="ts" setup name="Person">

import { ref, reactive } from 'vue'

// 数据
let car = reactive({
brand: '奔驰',
price: 100
})

let sum = ref(0)

// ref和reactive都可以将对象设置为响应式,但实现方式不一样
console.log(car) // RefImpl {__v_isShallow: false, dep: undefined, __v_isRef: true, _rawValue: {…}, _value: Proxy(Object)}

function changeCar() {
// car = { brand: 'bwm', price: 1 } 这样没有效果
// car = reactive({ brand: 'bwm', price: 1 }) 这样没有效果
Object.assign(car,{ brand: 'bwm', price: 1 })
// 但是,如果是使用的ref,就可以使用car.value={}来实现直接修改
}

function changePrice() {
car.price += 10
console.log(car.price)
}

function changeSum() {
sum.value += 1
}
</script>

<style scoped>
.person {
background-color: blue;
box-shadow: 0 0 10px;
padding: 20px;
}

button {
margin: 0 5px;
}
</style>

toRefs

将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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<template>
<div class="person">
<h2>姓名{{ person.name }}</h2>
<h2>年龄{{ age }}</h2>
<button @click="changeName">修改姓名</button>
<button @click="changeAge">修改年龄</button>
</div>
</template>

<script lang="ts" setup name="Person">

import { ref, reactive, toRefs } from 'vue'

let person = reactive({
name: '张三',
age: 18
})

// 用toRefs可以实现解构出响应式数据
// 且解构出,和原来的对象依然相关联,也就是说name和person.name相关
let { name, age } = toRefs(person)
console.log(name, age)

function changeAge() {
age.value += 1
}

function changeName() {
name.value += '~'
}

</script>

<style scoped>
.person {
background-color: blue;
box-shadow: 0 0 10px;
padding: 20px;
}

button {
margin: 0 5px;
}
</style>

计算属性

写法还是有两种,一种是箭头函数,但是相当于只读,只有get,写上set,就可以读写了

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
39
40
41
42
43
44
45
46
47
48
49
50
51
<template>
<div class="person">
姓: <input type="text" v-model="firstName"> <br>
名: <input type="text" v-model="lastName"> <br>
全名: <span>{{ fullName }}</span>
<button @click="changeFullName">将全民改为li-si</button>
</div>
</template>

<script lang="ts" setup name="Person">

import { ref, computed } from 'vue'

let firstName = ref('张')
let lastName = ref('三')

let fullName = computed({
get() {
return firstName.value.slice(0, 1).toUpperCase() + firstName.value.slice(1) + '-' + lastName.value
},
set(val) {
// const fval = val.split('-')[0]
// const lval = val.split('-')[1]
// firstName.value = fval
// lastName.value = lval
// 解构赋值
const [str1,str2] = val.split('-')
firstName.value = str1
lastName.value = str2
console.log('set', val)
}
})

function changeFullName() {
fullName.value = 'li-si'
}


</script>

<style scoped>
.person {
background-color: skyblue;
box-shadow: 0 0 10px;
padding: 20px;
}

button {
margin: 0 5px;
}
</style>

watch

vue3中的watch只能监视以下四种数据

  1. ref定义的数据
  2. reactive定义的数据
  3. 函数返回一个值
  4. 一个包含上述内容的数组

情况一

监视ref定义的基本类型数据:直接写数据名即可,监视的是其value值的改变

1
2
3
4
watch(sum, (newValue, oldValue) => {
console.log("sum变化了")
console.log(newValue, oldValue)
})

停止监视:

1
2
3
4
5
6
7
8
const stopWatch = watch(sum, (newValue, oldValue) => {
console.log("sum变化了")
console.log(newValue, oldValue)
// 停止监视
if (newValue >= 10) {
stopWatch()
}
})

情况二

监视ref定义的对象类型的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 直接写person,当修改属性时,并不会监视到,当修改整个对象时,才会监视到
// watch(person, (newValue, oldValue) => {
// console.log('person变化了')
// console.log(newValue, oldValue)
// })

// 若想监视到属性变化,需要开启深度监视
// 但是还有一个问题,如果是改对象的属性,那么newValue和oldValue是一样的数据,但是直接修改整个对象,就是不同的值了
// 大多数情况下是不管旧值的
watch(person, (newValue, oldValue) => {
console.log('person变化了')
console.log(newValue, oldValue)
}, { deep: true })
// immediate表示是否在初始化时就监视

情况三

监视reactive定义的对象类型的数据,且默认开启了深度监视

该深度监视无法关闭

1
2
3
4
5
// 监视reactive定义的对象,是默认开启深度监视的
watch(person, (newValue) => {
console.log('person变化了')
console.log(newValue)
})

情况四

监视ref或reactive定义的对象类型数据的某个属性,注意点如下

  1. 若该属性不是对象类型,需要写成函数形式
  2. 若该属性值是对象类型,可直接编,也可写成函数式,不过建议写成函数

第一种:

1
2
3
4
// 想要只监视person中的属性变化
watch(() => { return person.name }, (newValue) => {
console.log("person.name变化了")
})

箭头函数如果只有return语句,那么花括号和return可以省略

1
2
3
watch(() => person.name, (newValue) => {
console.log("person.name变化了")
})

第二种:
对象属性仍然是对象,那么可以直接写,也可以写成函数式

1
2
3
4
5
6
7
8
9
10
// 这么写,可以监视到属性变化,但是对象整体变化并不会监视到
watch(person.car, (newValue) => {
console.log("person.car变化了")
})

// 这么写,只能监视到整体变化
watch(() => person.car, (newValue) => {
console.log("person.car变化了")
},{deep:true})
// 但是加上深度监视,就可以监视到属性变化

结论就是,如果监视的是对象的对象属性,那么最好写成函数式,需要深度监视就写上deep

情况五

监视上述多个数据

就是直接拿数组包起来,然后看是写箭头函数还是写普通属性

1
2
3
watch([() => person.name, () => person.car.c1], () => {
console.log("person.name或person.car.c1变化了")
})

总结
实际用到时情况一和情况四较常用,也就是基本类型和监视对象

watchEffect

当监视多个属性时,使用watch要写多个,是比较麻烦的

1
2
3
4
5
6
7
// 当水温达到60度,或水位达到80时,给服务器发请求
watch([temp, height], (value) => {
let [newTemp, newHeight] = value
if (newTemp >= 60 || newHeight >= 80) {
console.log("发送请求")
}
})

使用watchEffect

相当于监听函数中用到的属性,变化时就会执行整个函数

1
2
3
4
5
watchEffect(() => {
if (temp.value >= 60 || height.value >= 80) {
console.log("send")
}
})

标签的ref属性

可以使用在普通html标签上,或者dom元素,也可以使用在组件上,来获取组件实例,但是默认是拿不到组件内的实例数据的,可以在组件的最后使用defineExpose来暴露数据

Person.vue

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
<template>
<div class="person">
<h1>中国</h1>
<!-- <h2 id="title2">beijing</h2> -->
<h2 ref="title2">beijing</h2>
<button @click="showLog">点我输出h2元素</button>
</div>
</template>

<script lang="ts" setup name="Person">
import { ref } from 'vue'

// 创建一个title2,用于存储ref标记的内容
let title2 = ref()
let a = ref(2)

function showLog() {
// 如果使用id,id如果和其他组件重复,会出问题(可能加载到其他组件的元素)
// console.log(document.getElementById('title2')?.innerText)
console.log(title2.value)
}

defineExpose({ a })
</script>

<style scoped>
.person {
background-color: skyblue;
box-shadow: 0 0 10px;
padding: 20px;
}

button {
margin: 0 5px;
}
</style>

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<Person ref="ren"></Person>
<button @click="showLog">测试</button>
</template>

<script lang="ts" setup name="App">
import Person from './components/Person.vue'

import { ref } from 'vue'

let ren = ref()

function showLog() {
console.log(ren.value.a)
}

</script>

<style></style>

TS中的接口,自定义类型,泛型

注意写法

ts文件:

1
2
3
4
5
6
7
8
9
10
11
12
// 定义一个接口,用于限制Person对象的具体属性
export interface PersonInter {
id: string,
name: string,
age: number,
x?: number // 表示x可有可无
}

// 一个自定义类型
export type Persons = Array<PersonInter>
// 下面这种也可以写
// export type Persons = PersonInter[]

使用时(导入时,如果ts的文件名是index.ts,那么就可以省去文件名,直接写到文件夹的名字即可)

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
39
40
41
42
<template>
<div class="person">
???
</div>
</template>

<script lang="ts" setup name="Person">
// @符表示src目录
import { type PersonInter, type Persons } from '@/types'

let person: PersonInter = { id: '312231123', name: '张三', age: 60 }

let personList1: Array<PersonInter> = [
{ id: '312231131223', name: '张三', age: 60 },
{ id: '31223112123', name: '张1', age: 61 },
{ id: '312231213123', name: '张2', age: 62 },
{ id: '312231113223', name: '张3', age: 63 }
]

let personList2: Persons = [
{ id: '312231131223', name: '张三', age: 60 },
{ id: '31223123', name: '张1', age: 61 },
{ id: '312231213123', name: '张2', age: 62 },
{ id: '312231113223', name: '张3', age: 63 }
]

console.log(personList1)
console.log(personList2)

</script>

<style scoped>
.person {
background-color: skyblue;
box-shadow: 0 0 10px;
padding: 20px;
}

button {
margin: 0 5px;
}
</style>

涉及到响应式的数据,有别的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { type Persons } from '@/types'

// 这种写法,当属性写错时,只会提示在personList上,并不会标注在字段上
let personList: Persons = reactive([
{ id: '1', name: '张三', age: 18 },
{ id: '2', name: '里斯', age: 19 },
{ id: '3', name: '王五', age: 20 }
])

// 会标注在字段上
let personList = reactive<Persons>([
{ id: '1', name: '张三', age: 18 },
{ id: '2', name: '里斯', age: 19 },
{ id: '3', name: '王五', age: 20 }
])

props

props的几种写法
父组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<Person :list="personList" />
</template>

<script lang="ts" setup name="App">
import Person from './components/Person.vue'
import { reactive } from 'vue'
import { type Persons } from '@/types'

let personList = reactive<Persons>([
{ id: '1', name: '张三', age: 18 },
{ id: '2', name: '里斯', age: 19 },
{ id: '3', name: '王五', age: 20 }
])

console.log(personList)

</script>

<style></style>

子组件

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
39
40
41
42
43
<template>
<div class="person">
<ul>
<li v-for="l in list" :key="l.id">姓名:{{ l.name }},年龄:{{ l.age }},</li>
</ul>
</div>
</template>

<script lang="ts" setup name="Person">
import { type Persons } from '@/types'
import { defineProps, withDefaults } from 'vue'

// 接收a,在插值语法中能直接使用,但是在脚本中无法直接使用
// defineProps(['a'])

// 使用变量接收
// let x = defineProps(['list'])
// console.log('@', x)

// 接收List加限制类型
// defineProps<{ list: Persons }>()

// 接收,限制类型,限制必要性 ,指定默认值
// 加上?就可以实现非必须
// 加上withDefaults就可以设置默认值
withDefaults(defineProps<{ list?: Persons }>(), {
list: () => [{ id: '1', name: '涨啊', age: 1 }]
})


</script>

<style scoped>
.person {
background-color: skyblue;
box-shadow: 0 0 10px;
padding: 20px;
}

button {
margin: 0 5px;
}
</style>

生命周期

分为4种,创建,挂载,更新,卸载,而创建,就是setup

父子组件的挂载顺序: 子先挂载,父后挂载

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<template>
<div class="person">
<h2>当前求和为:{{ sum }}</h2>
<button @click="add">点击加一</button>
</div>
</template>

<script lang="ts" setup name="Person">
import { onBeforeMount, onBeforeUnmount, onBeforeUpdate, onMounted, onUnmounted, onUpdated, ref } from 'vue'

let sum = ref(0)

function add() {
sum.value += 1
}
// 创建 现在的setup就是创建了

// 挂载前
onBeforeMount(() => {
console.log('挂载前')
})

// 挂载完毕
onMounted(() => {
console.log('挂载完毕')
})

// 更新前
onBeforeUpdate(() => {
console.log('更新前')
})

// 更新完毕
onUpdated(() => {
console.log('更新完毕')
})

// 卸载前
onBeforeUnmount(()=>{
console.log('卸载前')
})

// 卸载完毕
onUnmounted(()=>{
console.log('卸载完毕')
})

// 父子组件的挂载顺序: 子先挂载,父后挂载

</script>

<style scoped>
.person {
background-color: skyblue;
box-shadow: 0 0 10px;
padding: 20px;
}

button {
margin: 0 5px;
}
</style>

常用的钩子

  1. 挂载完毕
  2. 更新完毕
  3. 卸载之前

hooks

所以你明白了vue3的组合式api有什么用了吧,把一个功能的数据和方法甚至钩子都能写到一个ts文件中

注意hooks的命名,以及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
<template>
<div class="person">
<h2>当前求和为:{{ sum }}</h2>
<h2>放大:{{ bigSum }}</h2>
<button @click="add">点我sum+1</button>
<hr>
<img v-for="dog in dogList" :src="dog">
<button @click="getDog">再来一只狗</button>
</div>
</template>

<script lang="ts" setup name="Person">

import useSum from '@/hooks/useSum'
import useDog from '@/hooks/useDog'

const { sum, add, bigSum } = useSum()
const { dogList, getDog } = useDog()

</script>

<style scoped>
img {
height: 100px;
margin-right: 10px;
}

.person {
background-color: skyblue;
box-shadow: 0 0 10px;
padding: 20px;
}

button {
margin: 0 5px;
}
</style>

两个use

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

export default function () {
// 数据
let sum = ref(0)

let bigSum = computed(() => {
return sum.value * 10
})

onMounted(() => {
add()
})

function add() {
sum.value += 1
}
// 提供
return { sum, add, bigSum }
}
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
import { onMounted, reactive } from 'vue'
import axios from 'axios'

export default function () {
let dogList = reactive([
'https://images.dog.ceo/breeds/pembroke/n02113023_6140.jpg'
])

async function getDog() {
try {
let result = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')
dogList.push(result.data.message)
} catch (error) {
alert(error)
}
}

// 这里还可以写钩子
onMounted(() => {
getDog()
})

// 向外提供
return { dogList, getDog }
}

路由

基本使用

路由的配置文件

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
// 创建路由器并暴露

// 引入createRouter
import { createRouter, createWebHistory } from "vue-router"

// 引入要使用的组件
import Home from "@/components/Home.vue"
import News from "@/components/News.vue"
import About from "@/components/About.vue"

// 创建路由器
const router = createRouter({
history: createWebHistory(), // 定义路由器的工作模式
routes: [
{
path: '/home',
component: Home
},
{
path: '/news',
component: News
},
{
path: '/about',
component: About
}
]
})

export default router

然后去main.ts中要引入

1
2
3
import router from './router'

createApp(App).use(router).mount('#app')

使用RouterView和RouterLink来实现路由跳转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div class="app">
<h2>vue 路由测试</h2>
<!-- 导航区 -->
<div class="navigate">
<RouterLink active-class="active" to="/home">首页</RouterLink>
<RouterLink active-class="active" to="/news">新闻</RouterLink>
<RouterLink active-class="active" to="/about">关于</RouterLink>
</div>
<!-- 展示区 -->
<div class="main-content">
<RouterView></RouterView>
</div>
</div>
</template>

<script lang="ts" setup name="App">
import { RouterView,RouterLink } from "vue-router";
</script>

两个注意点

  1. 路由组件通常存放到pages或views文件夹下,一般组件通常存放在components文件夹
  2. 通过点击导航,视觉效果上消失了的路由组件,默认是被卸载的,需要的时候再去挂载

to的两种写法

一种直接写路径,一种给to加上冒号,然后写对象

1
2
3
4
5
<div class="navigate">
<RouterLink active-class="active" to="/home">首页</RouterLink>
<RouterLink active-class="active" to="/news">新闻</RouterLink>
<RouterLink active-class="active" :to="{ path: '/about' }">关于</RouterLink>
</div>

命名路由

其实就是给路由的配置上起名字,然后跳转时可以使用对象形式指定路由名

1
2
3
4
5
{
name: 'zhuye',
path: '/home',
component: Home
},
1
2
3
4
5
<div class="navigate">
<RouterLink active-class="active" to="/home">首页</RouterLink>
<RouterLink active-class="active" :to="{ name: 'xinwen' }">新闻</RouterLink>
<RouterLink active-class="active" :to="{ path: '/about' }">关于</RouterLink>
</div>

路由嵌套

这个很久之前用到过,后来写课设好像就再没用到过了哈哈啊哈操,注意子路由不写斜杠

1
2
3
4
5
6
7
8
9
10
11
12
{
name: 'xinwen',
path: '/news',
component: News,
children: [
{
// 子路由不写斜杠
path: 'detail',
component: Detail
}
]
},

路由传参

query参数

两种写法,第一种拼接,太麻烦了,如果传参比较多,还是用对象形式比较好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<li v-for="news in newsList" :key="news.id">
<!-- 第一种写法 -->
<!-- <RouterLink :to="`/news/detail?id=${news.id}&title=${news.title}&content=${news.content}`">{{ news.title }}
</RouterLink> -->
<!-- 第二种写法 -->
<RouterLink :to="{
path: '/news/detail',
query: {
id: news.id,
title: news.title,
content: news.content,
}
}">
{{ news.title }}
</RouterLink>
</li>

接收时,要使用useRoute

1
2
3
4
5
6
import { toRefs } from "vue";
import { useRoute } from "vue-router";
// 取出路由对象?算是吧 其中有一个query,里面就是路径参数
const route = useRoute()
// 直接解构响应式数据,会丢失响应式
let { query } = toRefs(route)

然后就可以使用query来获取数据了

1
2
3
4
5
<ul>
<li>编号: {{ query.id }}</li>
<li>标题: {{ query.title }}</li>
<li>内容: {{ query.content }}</li>
</ul>

params

这个东西说实话没有query好用,我还以为是请求体那种呢

用的时候要把参数拼接到路由配置文件中

注意事项:

  1. 如果想要传递params参数,只能使用name
  2. path中拼接参数
  3. 无法传递对象和数组,只能传递基本类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
name: 'xinwen',
path: '/news',
component: News,
children: [
{
// 如果想要传递params参数,只能使用name
name: 'xiangqing',
// params要求参数拼接在此,如果遇到可选参数,可在参数后加问好?
path: 'detail/:id/:title/:content',
component: Detail
}
]
},

传递时依然是两种写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<li v-for="news in  newsList " :key="news.id">
<!-- 第一种写法 -->
<!-- <RouterLink :to="`/news/detail/${news.id}/${news.title}/${news.content}`">{{ news.title }}</RouterLink> -->
<!-- 第二种写法 -->
<RouterLink :to="{
// 如果想要传递params参数,只能使用name
// 且params不能传递对象和数组,只能传递基本类型
name: 'xiangqing',
params: {
id: news.id,
title: news.title,
content: news.content
}
}">{{ news.title }}</RouterLink>

</li>

接收和query类似

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<ul>
<li>编号: {{ params.id }}</li>
<li>标题: {{ params.title }}</li>
<li>内容: {{ params.content }}</li>
</ul>
</template>

<script lang="ts" setup name="Detail">
import { toRefs } from "vue";
import { useRoute } from "vue-router";
// 取出路由对象?算是吧 其中有一个query,里面就是路径参数
const route = useRoute()
let {params} = toRefs(route)
</script>

<style></style>

路由props

现在的问题就是,接收的地方太麻烦了,props可以解决这个问题

可以实现直接使用,不显示接受,这个就相当于,在路由时,给路由组件传递了props

简单的使用

  1. 在路由的配置上加上props: true
  2. 路由组件接收propsdefineProps(['id', 'title', 'content'])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
name: 'xinwen',
path: '/news',
component: News,
children: [
{
// 如果想要传递params参数,只能使用name
name: 'xiangqing',
// params要求参数拼接在此,如果遇到可选参数,可在参数后加问好?
path: 'detail/:id/:title/:content',
component: Detail,
// 将路由收到的params参数作为props传递
props: true
}
]
},

但是这种写法,只能和params配合,并不能使用query参数


第二种写法:函数式写法,可自己决定将什么作为props给路由组件,主要用于query传递

注意props函数接收和返回的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
name: 'xinwen',
path: '/news',
component: News,
children: [
{
name: 'xiangqing',
path: 'detail',
component: Detail,
// query传递
props(route) {
return route.query
}
}
]
},

接收时仍一样


还有一种写法,就是直接用props,相当于在路由组件上写props,适合自定义数据,不依赖query和params

1
2
3
4
props:{
a:100,
b:200
}

replace

路由跳转默认是push,也就是带有历史记录的跳转

而replace,会清除之前的记录

1
<RouterLink replace active-class="active" to="/home">首页</RouterLink>

编程式路由导航

还是这个好用,甚至之前开发都几乎没用过标签来导航

用起来区别仅在于,不能使用this.router.push了,而要引入useRouter,然后获取到router来使用

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<template>
<ul>
<li v-for="news in newsList " :key="news.id">
<button @click="showNewsDetail(news)">查看新闻</button>
<!-- 第一种写法 -->
<!-- <RouterLink :to="`/news/detail/${news.id}/${news.title}/${news.content}`">{{ news.title }}</RouterLink> -->
<!-- 第二种写法 -->
<RouterLink :to="{
// 如果想要传递params参数,只能使用name
// 且params不能传递对象和数组,只能传递基本类型
name: 'xiangqing',
query: {
id: news.id,
title: news.title,
content: news.content
}
}">{{ news.title }}</RouterLink>

</li>
</ul>
<!-- 展示区 -->
<div class="news-content">
<RouterView></RouterView>
</div>
</template>

<script lang="ts" setup name="News">
import { reactive } from "vue"
import { RouterView, RouterLink, useRouter } from "vue-router"

const newsList = reactive([
{ id: '001', title: '十种食物', content: '你猜是什么' },
{ id: '002', title: '如何一夜暴富', content: '一步登天' },
{ id: '003', title: '好消息', content: '快过年了' }
])

// 用ts的接口限定传递的news参数
interface NewsInter {
id: string,
title: string,
content: string
}

const router = useRouter()
function showNewsDetail(news: NewsInter) {
router.push({
path: '/news/detail',
query: {
id: news.id,
title: news.title,
content: news.content
}
})
}

</script>

<style></style>

重定向

这玩意没啥说的,不过我一般习惯直接将根目录设置为某个组件

1
2
3
4
{
path: '/',
redirect: '/home'
},

pinia

代替vue2中的vuex(vuex我都忘了怎么用了)

再说一下解构赋值

1
2
3
4
5
6
7
8
9
10
11
async function getLoveTalk() {
// 可以解构,不过写起来有点....
// let result = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
// ...... 这个解构的同时,将content命名为title
let { data: { content: title }
} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
let obj = {
id: nanoid(), title
}
talkList.unshift(obj)
}

let { data: { content: title }就是解构出result中的data,再从data中解构出content,并命名为title

下面的obj就可以直接写title了(title:title替换成title)

搭建pinia环境

npm i pinia就安装好了

然后到main.ts中引入配置

1
2
3
4
5
6
7
8
9
// 引入pinia
import { createPinia } from 'pinia'

// 创建pinia
const pinia = createPinia()

// 安装pinia,使用use就行了

createApp(App).use(pinia).mount('#app')

使用pinia

  1. 首先在src目录下创建store目录

  2. 然后创建组件名小驼峰的ts文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import { defineStore } from "pinia"

    export const useTalkLoveStore = defineStore('talkLove', {
    // 真正存储数据的地方
    state() {
    return {
    talkList: [
    { id: '001', title: '哈哈哈' },
    { id: '001', title: '嘿嘿嘿' },
    { id: '001', title: '呵呵呵' },
    ]
    }
    },
    })
  3. 引入useTalkLoveStore

    1
    2
    3
    import { useTalkLoveStore } from '@/store/talkLove'

    const talkLoveStore = useTalkLoveStore()

此时,这个talkLoveStore中就可以直接取到里面的数据了:talkLoveStore.talkList


修改数据的方法

  1. 就是直接修改
  2. 使用$patch,适用于批量修改
    1
    2
    3
    4
    5
    // 第二种修改方式,适用于批量修改
    countStore.$patch({
    address: '汉中',
    nickName: '灼灼某人'
    })
  3. 第三种,使用action(注意使用this来获取state中的数据)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import { defineStore } from "pinia"

    export const useCountStore = defineStore('count', {
    // 真正存储数据的地方
    actions: {
    // 里面放置的是多个动作函数,用于响应组件中的动作
    increment(n: any) {
    // 这里要使用this来获取state中的数据
    this.sum += n
    }
    },
    state() {
    return {
    sum: 1,
    address: 'hanzhong',
    nickName: 'zzmr'
    }
    },
    })

然后在组件中直接使用useCountStore来调用increment即可


但是现在每次读取数据时,都要使用store.xx,不够优雅

1
2
3
<h2>当前求和为:{{ countStore.sum }}</h2>
<h3>地址: {{ countStore.address }}</h3>
<h3>昵称: {{ countStore.nickName }}</h3>

所以可以使用解构赋值,但是直接解构会导致数据丢失响应式,但如果加上toRefs(),那又太多余了,因为只有store中的数据需要进行响应式,所以可以使用storeToRefs来只使store中的数据变成响应式.

1
2
3
4
5
6
import { storeToRefs } from 'pinia';
import { useCountStore } from '@/store/count'
const countStore = useCountStore()

// 优雅的解构赋值并将数据置为响应式
let { sum, address, nickName } = storeToRefs(countStore)

getters

对store中的数据进行处理后再返回

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
import { defineStore } from "pinia"

export const useCountStore = defineStore('count', {
// 真正存储数据的地方
actions: {
// 里面放置的是多个动作函数,用于响应组件中的动作
increment(n: number) {
this.sum += n
}
},
state() {
return {
sum: 1,
address: 'hanzhong',
nickName: 'zzmr'
}
},
getters: {
bigSum(state) {
return state.sum * 10
},
upperNickName(state) {
return this.nickName.toUpperCase()
}
}
})

其实就是加上getters配置,然后写函数,在使用时,可以直接将函数名当函数的返回值使用

1
2
3
<h3>bigSum:{{ bigSum }}</h3>
<h3>upperNickName:{{ upperNickName }}</h3>
let { sum, address, nickName, bigSum, upperNickName } = storeToRefs(countStore)

订阅

其实就是监视store中的数据变化

1
2
3
4
talkLoveStore.$subscribe((mutate,state)=>{
console.log('数据发生变化')
console.log(mutate,'@',state)
})

mutate是修改的操作,state中能取到store中的数据

store组合式写法

就是,就是去掉配置项,数据就直接定义,函数就可以直接使用变量
20240209180713

组件间通信

好像之前写的时候,基本就是本地存储+事件总线,而且后面写项目事件总线都用的少了哈哈哈操

props

父与子,子与父传递数据

父给子传,就直接:xxx="xxx"

1
<Child :car="car"/>

这样就能把父亲中的car传递给子组件,而子组件中需要接收:

1
defineProps(['car'])

这样子组件就能拿到父组件给的car了


子给父传,需要父提供一个方法,并将这个方法像参数一样传递给子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div class="father">
<h3>父组件</h3>
<h4>汽车:{{ car }}</h4>
<h4 v-show="toy">子给的玩具:{{ toy }}</h4>
<Child :car="car" :sendToy="getToy"/>
</div>
</template>

<script setup lang="ts" name="Father">
import Child from './Child.vue'
import {ref} from 'vue'
// 数据
let car = ref('奔驰')
let toy = ref('')
// 方法
function getToy(value:string){
toy.value = value
}
</script>

子组件中,接收方法后,直接调用该方法及传递参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div class="child">
<h3>子组件</h3>
<h4>玩具:{{ toy }}</h4>
<h4>父给的车:{{ car }}</h4>
<button @click="sendToy(toy)">把玩具给父亲</button>
</div>
</template>

<script setup lang="ts" name="Child">
import {ref} from 'vue'
// 数据
let toy = ref('奥特曼')
// 声明接收props
defineProps(['car','sendToy'])
</script>

这里是直接在template中调用了sendToy并传递了数据,如果要在script中写,就要接收到defineProps返回的数据来调用了

自定义事件

首先说一下就是一个点击事件,如果没有传递参数,是可以接受到事件的dom对象的,但是如果传递了参数,那就不能接收到dom元素了,此时可以使用$event来传递dom元素

1
<button @click="test(1, $event)">点我</button>

自定义事件,常用于子传父
父组件如下,就是在父组件中给子组件标识一个@sendToy="getToy",因为是@开头,所以是事件的意思,@什么,子组件就可以使用什么方法,=的方法就是这个事件的回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div class="father">
<h3>父组件</h3>
<h4>{{ str }}</h4>
<Child @sendToy="getToy" />
</div>
</template>

<script setup lang="ts" name="Father">
import Child from './Child.vue'
import { ref } from 'vue'

let str = ref('你好')

function getToy(toy: string) {
str.value = toy
}
</script>

子组件如下,要使用defineEmits(['sendToy'])来接收,然后使用emit('sendToy',参数)来触发自定义事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div class="child">
<h2>子组件</h2>
<h3>玩具:{{ toy }}</h3>
<button @click="emit('sendToy',toy)">发送玩具</button>
</div>
</template>

<script setup lang="ts" name="Child">
import { ref } from "vue";
let toy = ref('奥特曼')

const emit = defineEmits(['sendToy'])

</script>

mitt

把这玩意当成vue2的事件总线

安装:npm i mitt

创建一个工具类?名叫emitter.ts

1
2
3
4
5
6
7
// 引入mitt
import mitt from "mitt"

// 调用mitt并暴露
const emitter = mitt()

export default emitter

emitter有以下的函数:
20240209205120

方法使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 绑定事件
emitter.on('test1', () => {
console.log('test1被调用了')
})

emitter.on('test2', () => {
console.log('test2被调用了')
})

// 触发事件
setInterval(() => {
emitter.emit('test1')
emitter.emit('test2')
}, 2000)

setTimeout(() => {
// 解绑
emitter.off('test1')
// 直接清空
// emitter.all.clear()
}, 5000)

组件间通信实现
还是这个简单还用,记得使用前导入依赖:import emitter from '@/utils/emitter';

1
2
3
4
5
6
7
8
/**
* 孩子1把玩具交给孩子2
*/
let toy = ref('奥特曼')

function sendToy() {
emitter.emit('toy', toy.value)
}

孩子二接收,在卸载组件时解绑事件

1
2
3
4
5
6
7
8
9
10
11
let toy = ref('')

// 给孩子2绑定事件,接收数据
emitter.on('toy', (value: any) => {
toy.value = value
})

// 解绑事件
onUnmounted(()=>{
emitter.off('toy')
})

v-model

一般不会自己写组件然后用这个来传递数据,不过ui组件库大量使用的就是这个

这玩意还挺神奇,之前用el-input时可以直接使用v-model来绑定数据,但是我们自己的组件并不能实现,底层是这么写的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div class="father">
<h3>父组件</h3>
<!-- v-model用在html标签上 -->
<!-- <input type="text" v-model="username"> -->
<!-- 上下的意思一样 -->
<!-- 断言解决报错,标识event.target就是html中的输入元素 -->
<!-- <input type="text" :value="username" @input="username = (<HTMLInputElement>$event.target).value"> -->
<!-- v-model用在组件上 -->
<ZzmrInput v-model="username" />
<!-- <ZzmrInput :modelValue="username" @update:modelValue="username = $event" /> -->

</div>
</template>

<script setup lang="ts" name="Father">
import { ref } from "vue"
import ZzmrInput from './ZzmrInput.vue'

let username = ref('zhangsan')

</script>

ZzmrInput.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<input type="text" :value="modelValue" @input="emit('update:modelValue', (<HTMLInputElement>$event.target).value)">
</template>

<script lang="ts" setup name="ZzmrInput">


defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

</script>

<style></style>

这里多次用到了$event

  • 对于原生事件:$event就是事件对象,能.target
  • 对于自定义事件:$event就是触发事件时,所传递的数据,不能.target

$attrs

$attrs用于实现当前组件的父组件,向当前组件的子组件通信:祖->孙

具体说明:$attrs是一个对象,包含所有父组件传入的标签属性

父传给子数据,子不接收,这个数据就存到了$attrs中,子再将$attrsv-bind="$attrs"传给孙组件,就实现了祖->孙

到时候我肯定不用,说不定还是mitt梭哈

$refs,$parent

$refs用于父传子
$parent用于子传父

到时候我肯定不用,说不定还是mitt梭哈+1

provide_inject

这玩意也是用于祖孙之间传递的,与$attrs不同在于,这个不需要打扰中间人,可以直接实现祖孙传递

插槽

之前好像也不怎么用这个玩意

默认插槽

总结就是,将组件标签写成双标签,然后里面写上内容,这就是插槽的内容,然后到组件中,写上slot标签,就是展示这些内容的位置

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>
<div class="father">
<div class="content">
<Game title="热门游戏">
<!-- 里面就是插槽的数据 -->
<ul>
<li v-for="item in games" :key="item.id">{{ item.name }}</li>
</ul>
</Game>
<Game title="今日美食">
<img :src="imageUrl">
</Game>
<Game title="影视推荐">
<video :src="videoUrl" controls></video>
</Game>
</div>
</div>
</template>

<script setup lang="ts" name="Father">
import Game from './Game.vue'
import { ref, reactive } from 'vue'

let games = reactive([
{ id: '001', name: 'war3' },
{ id: '002', name: 'rdr2' },
{ id: '003', name: 'gta5' }
])

let imageUrl = ref('http://47.109.139.173:9000/food.guide/b1ba1c68-7b43-4c9f-9082-09bfcc9d8c57.jpg')

let videoUrl = ref('http://47.109.139.173:9000/food.guide/20231017_222546.mp4')

</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div class="game">
<h2>{{ title }}</h2>
<!-- 挖坑,展示插槽的数据 -->
<slot></slot>
</div>
</template>

<script setup lang="ts" name="Game">

defineProps(['title'])

</script>

具名插槽

当一个插槽不能满足需求时,可以使用具名插槽,就是给插槽的内容加上名字,使用template分开,一个萝卜一个坑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<Game title="热门游戏">
<template v-slot:s1>
<ul>
<li v-for="item in games" :key="item.id">{{ item.name }}</li>
</ul>
</template>
<template v-slot:s2>
s2呼呼哈嘿
</template>
</Game>
<Game title="今日美食">
<template v-slot:s1>
<img :src="imageUrl">
</template>
</Game>
<Game title="影视推荐">
<template v-slot:s1>
<video :src="videoUrl" controls></video>
</template>
</Game>

slot标签上也加上名字

1
2
3
4
5
6
<div class="game">
<h2>{{ title }}</h2>
<slot name="s1">s1插槽默认内容</slot>
<br>
<slot name="s2">s2插槽默认内容</slot>
</div>

对于template上写v-slot:xx这种写法,可以简写成:#xx

1
2
3
4
5
6
<Game title="影视推荐">
<!-- 简写 -->
<template #s1>
<video :src="videoUrl" controls></video>
</template>
</Game>

作用域插槽

插槽的数据在插槽组件中,而结构或者说使用插槽的地方可以接收插槽的数据

插槽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div class="game">
<h2>游戏列表</h2>
<!-- 提供数据 -->
<slot :games="games"></slot>
</div>
</template>

<script lang="ts" setup name="Game">
import { reactive } from 'vue'
let games = reactive([
{ id: '001', name: 'war3' },
{ id: '002', name: 'rdr2' },
{ id: '003', name: 'gta5' }
])
</script>

使用插槽的地方,要使用v-slot="params",也可以解构赋值v-slot="{games}"

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>
<div class="father">
<h1>父组件</h1>
<div class="content">
<Game>
<template v-slot="params">
<ul>
<li v-for="g in params.games" :key="g.id">{{ g.name }}</li>
</ul>
</template>
</Game>
<Game>
<template v-slot="params">
<ol>
<li v-for="g in params.games" :key="g.id">{{ g.name }}</li>
</ol>
</template>
</Game>
<Game>
<!-- 可以直接解构 -->
<template v-slot="{games}">
<h3 v-for="g in games" :key="g.id">{{ g.name }}</h3>
</template>
</Game>
</div>
</div>
</template>

<script setup lang="ts" name="Father">
import { ref, reactive } from 'vue'
import Game from './Game.vue'
</script>

如果要给插槽加名字,那么可以在v-slot:xx="{xxx}",冒号后面就可以跟名字,也可以#xx="{xxx}"这么些

ui组件库中会大量使用这个作用域插槽

其他API

shallowRef与shallowReactive

shallowRef

  1. 作用:创建一个响应式数据,但只对顶层属性进行响应式处理
  2. 特点:只跟踪引用值的变化,不关心值内部的属性变化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 使用shallowRef,那么就只有person.value才是响应式的,更深层次的变化并不是响应式的,也就是说,整体变化是响应式的,属性变并不是响应式
let person = shallowRef({
name: '张三',
age: 18
})

// 使用shallowReactive,也是只能修改第一层,浅层的数据,像这个里面的brand和options整个对象,但是修改color和price就不行了
let car = shallowReactive({
brand: 'bwm',
options: {
color: '白色',
price: 10
}
})

readonly与shallowReadonly

readonly就是根据一个响应式数据生成一个只读的响应式数据,尝试修改只读数据的话,会报错
20240210172907

shallowReadonly是浅层次的只读,对于对象的第一层属性是只读的,但是深层的属性是可以修改的

toRaw

用于获取一个响应式对象的原始对象,toRaw返回的对象不再是响应式的,不会触发视图更新

官网描述:这是一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不触发更改的特殊方法,不建议保存对原始对象的持久引用,请谨慎使用

何时使用?在需要将响应式对象传递给非Vue的库或外部系统时,使用toRaw可以确保它们收到的是普通对象

1
2
3
function getRaw() {
console.log(toRaw(person))
}

markRaw

标记一个数据,让其不可成为响应式数据,如下,就算bookR使用了reactive,也无法变成响应式数据

例如使用mock.js时,为了防止误把mock.js变为响应式对象,可以使用markRaw去标记mock.js

1
2
3
4
5
6
7
8
9
10
let book = markRaw({
name: 'gc',
price: 1
})

let bookR = reactive(book)
function changeName(){
bookR.name = 's'
console.log(bookR)
}

customRef

作用:创建一个自定义的ref,并对其依赖项跟踪和更新触发进行逻辑控制

使用注意事项

  1. 创建一个初始数据
  2. 注意get中的track()调用,告诉vue数据msg很重要,要对msg进行持续关注,一旦msg变化就去更新
  3. 注意set中的trigger()调用,通知vue一下数据变化了

下面实现了一个防抖的效果,后面写项目可以考虑用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let initValue = '你好'
let timer: number
let msg = customRef((track, trigger) => {
return {
// msg被读取时调用
get() {
track() // 告诉vue数据msg很重要,要对msg进行持续关注,一旦msg变化就去更新
return initValue
},
// msg被修改时调用
set(value) {
clearTimeout(timer)
timer = setTimeout(() => {
initValue = value
trigger() // 通知vue一下数据变化了
}, 1000)

}
}
})

但一般开发会将自定义的ref封装到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
import { customRef } from 'vue'

export default function (initValue: string, delay: number) {
let timer: number

let msg = customRef((track, trigger) => {
return {
// msg被读取时调用
get() {
track() // 告诉vue数据msg很重要,要对msg进行持续关注,一旦msg变化就去更新
return initValue
},
// msg被修改时调用
set(value) {
clearTimeout(timer)
timer = setTimeout(() => {
initValue = value
trigger() // 通知vue一下数据变化了
}, delay)
}
}
})
return { msg }
}

然后组件中直接调用

1
2
3
4
import useMegRefs from '@/hooks/useMegRefs'

// 使用封装的hooks useMegRefs
let { msg } = useMegRefs('zzmr', 2000)

Vue3新组件

Teleport

这玩意?我会用得到?

就是可以把<teleport>标签中包裹的标签,传送到指定的标签内:

1
2
3
4
5
6
7
<teleport to="body">
<div class="modal" v-show="isShow">
<h2>弹窗标题</h2>
<h2>弹窗内容</h2>
<el-button @click="isShow = false">关闭弹窗</el-button>
</div>
</teleport>

App.vue中,Modal是放到app的div下的,但是使用了teleport就可以直接将这个玩意传送到body下

1
2
3
4
<div class="app">
<img src="http://47.109.139.173:9000/food.guide/b1ba1c68-7b43-4c9f-9082-09bfcc9d8c57.jpg" alt="">
<Modal></Modal>
</div>

Suspense

等待异步组件时渲染一些额外内容,让应用有更好的用户体验

20240210194044

子组件中包含着异步任务,在请求成功之前,用于展示一些东西,给用户更好的体验,但是无法作为骨架屏使用,而且这个东西是一个实验性的组件,未来可能移除

全局API转移到应用对象

  • app.component
  • app.config
  • app.directive
  • app.mount
  • app.unmount
  • app.use

其他

  • 过渡类名 v-enter 修改为 v-enter-from、过渡类名 v-leave 修改为 v-leave-from

  • keyCode 作为 v-on 修饰符的支持。

  • v-model 指令在组件上的使用已经被重新设计,替换掉了 v-bind.sync。

  • v-ifv-for 在同一个元素身上使用时的优先级发生了变化。

  • 移除了$on$off$once 实例方法。

  • 移除了过滤器 filter

  • 移除了$children 实例 propert