Vue3教程

Vue教程

Vue基础

1. 创建Vue3(Vue3)工程

vite(推荐)

具体操作(官方文档)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
## 1.创建命令
npm create vue@latest

## 2.具体配置
## 配置项目名称
project name:vue3_test
## 是否添加ts(TypeScript)支持
Add TypeScript? Yes
## 是否添加JSX支持
Add JSX Support? No
## 是否添加路由环境
Add Vue Router for Single Page Application development? No
## 是否添加pinia环境
Add Pinia for state management? No
## 是否添加单元测试
Add Vitest for Unit Testing? No
## 是否添加端对端测试方案
Add an End-to-End Testing Solution? No
## 是否添加ESLint语法检测
Add ESLint for code quality? Yes
## 是否添加Prettier代码格式
Add Prettier for code formatting? No

2. 介绍Vue3文件

.vscode:存放Vue官方推荐的插件

public:页签图标

src:源代码文件

​ main.ts:

1
2
3
4
import { createApp } from 'vue' //createAPP创建一个APP
import App from './App.vue' //引用App根组件

createApp(App).mount('#app') //App创建一个前端应用的根 mount挂载 #app名为app的容器(在index.html里的<div id="app"></div>)

​ App.vue:根组件

​ components:组件

.gitignore:git忽略文件

env.d.ts:让ts认识.txt、.jpg等文件

inedx.html:入口文件(通过引用src里的main.ts来运行src里面内容)

README.md:工程介绍文件

package类文件:依赖声明文件

tsconfig类文件:ts配置文件

vite.config.ts:整个项目配置文件

Tip:使用npm i一键补全依赖

package.json可以查看命令(如启动命令npm run dev)

3. .Vue文件

大概模版

1
2
3
4
5
6
7
8
9
10
11
<template>
<!-- html 结构 -->
</template>

<script>
//JS或TS代码 交互
</script>

<style>
/* 样式 */
</style>

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div class="app">
<h1>hello</h1>
</div>
</template>

<script>
export default{
name: 'App' //组件名
}
</script>

<style>
.app{
background-color: #ddd;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
</style>

4. 语法

4.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
<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>
export default {
name: 'Person', //组件名称
data() { //数据
return {
name: '张三',
age: 18,
tel: '123456789'
}
},
methods: { //函数方法实现
showTel() {
alert(this.tel)
},
changeName() {
this.name = 'zhangsan'
},
changeAge() {
this.age += 1
}
}
}
</script>

//scoped属性可以让样式只在当前组件生效
<style>
.person {
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
button{
margin: 0 5px;
}
}
</style>

4.2 Vue3核心语法

4.2.1 OptionsAPI与CompositionAPI
  • vue2的API设计是Options(配置)风格
  • vue3的API设计是Composition(组合)风格
4.2.1.1 OptionalAPI的弊端

Options类型的API,数据、方法、计算属性等,是分散在:data、methods、computed中的,若想增或者修改一个需求,就需要分别修改:data、methods、computed,不便于维护和复用

4.2.1.2 CompositionAPI的优势

可以用函数的方法,更加优雅的组织代码,让相关功能的代码更加有序的组织在一起

4.2.2 setup

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

特点:

  • setup函数返回的对象中的内容,可直接在模板中使用
  • setup中访问this是undefined
  • setup函数会在beforeCreate之前调用,它是领先所有钩子先执行
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
<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>
export default {
name: 'Person',
setup() {
// console.log(this) //此时this是undefined
//数据
let name = '张三' //此时name不是响应式数据
let age = 18 //此时age不是响应式数据
let tel = '1234567890' //此时tel不是响应式数据

//方法
function changeName() {
name = '李四' //这样修改name页面不会发生变化,但name的值已经改变
}
function changeAge() {
age += 1
}
function showTel() {
alert(tel)
}

return {name,age,changeName,changeAge,showTel} //要返回出去才能生效或者可以取个别名如name使用a:name
// return () => 'hh' setup也可以直接返回渲染函数(此为箭头函数)
}
}
</script>

<style>
.person {
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
button{
margin: 0 5px;
}
}
</style>

data、methods和setup可以同时存在,data可以读取setup数据要使用this.,setup无法读取data ++

setup语法糖

1
2
3
<scripts setup>
let a = 123 //自动返回
</scripts>
4.2.3 ref基本类型的响应式数据和reactive对象类型的响应式数据
4.2.3.1 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
<template>
<div class="person">
<h2>姓名:{{ name }}</h2> <!-- 自动转换为name.value -->
<h2>年龄:{{ age }}</h2>
<button @click="changeName">修改姓名</button>
<button @click="changeAge">修改年龄</button>
<button @click="showTel">查看联系方式</button>
</div>
</template>

<script>
export default {
name: 'Person',
}
</script>

<script setup>
import { ref } from 'vue' //添加ref包

//数据
let name = ref('张三') //为需要响应式改变的数据套上ref
let age = ref(18)
let tel = '1234567890' //此时tel不是响应式数据

//方法
function changeName() {
name.value = '李四'
}
function changeAge() {
age.value += 1
}
function showTel() {
alert(tel)
}
</script>

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

button {
margin: 0 5px;
}
}
</style>
4.2.3.2 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
<template>
<div class="person">
<h2>一辆{{ car.brand }},价值{{ car.price }}</h2>
<button @click="changePrice">修改汽车价格</button>
</div>
</template>

<script>
export default {
name: 'Person',
}
</script>

<script setup>
import { reactive } from 'vue' //引入reactive

//数据
let car = reactive({ //reactive创建响应式对象
brand: 'BMW',
price: 100
})

//方法
function changePrice() {
car.price += 10
}

</script>

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

button {
margin: 0 5px;
}
}
</style>
4.2.3.3 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
<template>
<div class="person">
<h2>一辆{{ car.brand }},价值{{ car.price }}</h2>
<button @click="changePrice">修改汽车价格</button>
</div>
</template>

<script>
export default {
name: 'Person',
}
</script>

<script setup>
import { ref } from 'vue' //ref

//数据
let car = ref({ //ref创建响应式对象
brand: 'BMW',
price: 100
})

//方法
function changePrice() {
car.value.price += 10 //修改需要加.value数据
}

</script>

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

button {
margin: 0 5px;
}
}
</style>
4.2.3.4 ref和reactive的区别

宏观角度:

  1. ref用来定义:基本数据类型、对象数据类型
  2. reactive用来定义:对象类型数据

区别:

  1. ref创建的变量必须使用.value(使用插件vue-official可以配置自动加value)
  2. reactive重新分配一个对象,会失去响应式(可使用Object.assign去整体替换) Object.assign(car,{brand:’123’,price:’12’}) 直接用car={brand:’123’,price:’12’}响应式会失效,也不能加reactive包裹。

使用原则:

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

让其结构出来的依旧拥有响应式数据且与原数据绑定

toRefs可以解构全部的

toRef可以解构指定的

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
<template>
<div class="person">
<h2>姓名:{{ person.name }}</h2>
<h2>年龄:{{ person.age }}</h2>
<button @click="changeName">修改姓名</button>
<button @click="changeAge">修改年龄</button>
</div>
</template>

<script>
export default {
name: 'Person',
}
</script>

<script setup>
import { reactive,toRefs,toRef } from 'vue';

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

let {name,age} = toRefs(person)
let ages = toRef(person,'age')

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

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

</script>

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

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

计算属性有缓存

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
<template>
<div class="person">
姓:<input type="text" v-model="firstName"> <br>
名:<input type="text" v-model="lastName"> <br>
姓名:<span>{{ fullName }}</span> <br>
<button @click="changeFullName">修改全名</button>
</div>
</template>

<script>
export default {
name: 'Person',
}
</script>

<script setup>
import { ref,computed } from 'vue';
let firstName = ref('ma');
let lastName = ref('yun');

//这么定义的fullName是一个计算属性,且只能读取,不能修改
// let fullName = computed(()=>{
// return firstName.value.slice(0,1).toUpperCase() + firstName.value.slice(1) + '-' + lastName.value
// })

//可读可写的fullName
let fullName = computed({
get() {
return firstName.value.slice(0,1).toUpperCase() + firstName.value.slice(1) + '-' + lastName.value
},
set(newValue) {
let [first,last] = newValue.split('-');
firstName.value = first;
lastName.value = last;
}
})

function changeFullName() {
fullName.value = 'Ma-Yun';
}

</script>

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

button {
margin: 0 5px;
}
}
</style>
4.2.6 watch监视
  • 作用:监视数据的变化
  • 特点:vue3中的watch只能监视四种数据
    1. ref定义的数据
    2. reactive定义的数据
    3. 函数返回一个值(getter函数)
    4. 一个包含上述内容的数组

watch第一个参数是:被监视的数据

watch第二个参数是:监视的回调

watch的第三个参数是:配置对象(deep,immediate等等)

4.2.6.1情况一

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

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">
<h1>监视情况一:</h1>
<h2>当前求和为:{{ sum }}</h2>
<button @click="addSum">+1</button>
</div>
</template>

<script>
export default {
name: 'Person',
}
</script>

<script setup>
import { ref,watch } from 'vue'
//数据
let sum = ref(0)
//方法
function addSum() {
sum.value++
}
//监视情况一
const stopWatch = watch(sum, (newValue, oldValue) => {
console.log(newValue, oldValue)
if (newValue > 10) {
stopWatch()
}
})
</script>

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

button {
margin: 0 5px;
}
}
</style>
4.2.6.2情况二

监视ref定义的对象类型数据:直接写数据名,监视的是对象的地址值,若想监视对象的内部数据,要手动开启深度监视

修改ref对象属性,new和old都是新值,因为是同一个对象

若修改整个ref定义的对象,new是新值,old是旧值,因为不是同一个对象

immediate为true开始立即监视

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<template>
<div class="person">
<!-- <h1>监视情况一:</h1>
<h2>当前求和为:{{ sum }}</h2>
<button @click="addSum">+1</button> -->
<h1>监视情况二:</h1>
<h2>姓名:{{ person.name }}</h2>
<h2>年龄:{{ person.age }}</h2>
<button @click="changeName">修改姓名</button>
<button @click="changeAge">修改年龄</button>
<button @click="changePerson">修改整个</button>
</div>
</template>

<script>
export default {
name: 'Person',
}
</script>

<script setup>
import { ref,watch } from 'vue'
// //数据
// let sum = ref(0)
// //方法
// function addSum() {
// sum.value++
// }
// //监视情况一
// const stopWatch = watch(sum, (newValue, oldValue) => {
// console.log(newValue, oldValue)
// if (newValue > 10) {
// stopWatch()
// }
// })

//数据
let person = ref({
name: '张三',
age: 18
})
//方法
function changeName() {
person.value.name += '~'
}
function changeAge() {
person.value.age += 1
}
function changePerson() {
person.value = {
name: '李四',
age: 20
}
}

//监视情况二 监视对象的地址值
watch(person, (newValue, oldValue) => {
console.log(newValue, oldValue)
})
//监视情况二 监视对象内部属性变化
watch(person, (newValue, oldValue) => {
console.log(newValue, oldValue)
}, { deep: true ,immediate:true})

</script>

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

button {
margin: 0 5px;
}
}
</style>
4.2.6.3情况三

监视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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
<template>
<div class="person">
<!-- <h1>监视情况一:</h1>
<h2>当前求和为:{{ sum }}</h2>
<button @click="addSum">+1</button> -->
<!-- <h1>监视情况二:</h1>
<h2>姓名:{{ person.name }}</h2>
<h2>年龄:{{ person.age }}</h2>
<button @click="changeName">修改姓名</button>
<button @click="changeAge">修改年龄</button>
<button @click="changePerson">修改整个</button> -->
<h1>监视情况三:</h1>
<h2>姓名:{{ person.name }}</h2>
<h2>年龄:{{ person.age }}</h2>
<button @click="changeName">修改姓名</button>
<button @click="changeAge">修改年龄</button>
<button @click="changePerson">修改整个</button>
</div>
</template>

<script>
export default {
name: 'Person',
}
</script>

<script setup>
import { reactive,watch } from 'vue';
//import { ref,watch } from 'vue'
// //数据
// let sum = ref(0)
// //方法
// function addSum() {
// sum.value++
// }
// //监视情况一
// const stopWatch = watch(sum, (newValue, oldValue) => {
// console.log(newValue, oldValue)
// if (newValue > 10) {
// stopWatch()
// }
// })

// //数据
// let person = ref({
// name: '张三',
// age: 18
// })
// //方法
// function changeName() {
// person.value.name += '~'
// }
// function changeAge() {
// person.value.age += 1
// }
// function changePerson() {
// person.value = {
// name: '李四',
// age: 20
// }
// }

// //监视情况二 监视对象的地址值
// watch(person, (newValue, oldValue) => {
// console.log(newValue, oldValue)
// })
// //监视情况二 监视对象内部属性变化
// watch(person, (newValue, oldValue) => {
// console.log(newValue, oldValue)
// }, { deep: true ,immediate:true})

//数据
let person = reactive({
name: '张三',
age: 18
})
//方法
function changeName() {
person.name += '~'
}
function changeAge() {
person.age += 1
}
function changePerson() {
Object.assign(person, {
name: '李四',
age: 20
})
}
//监视情况三
watch(person, (newValue, oldValue) => {
console.log(newValue, oldValue)
})

</script>

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

button {
margin: 0 5px;
}
}
</style>
4.2.6.4情况四

监视ref或reactive定义的对象数据的某个属性,注意:

  1. 若该属性不是对象类型,需要写成函数形式
  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
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<template>
<div class="person">
<h2>姓名:{{ person.name }}</h2>
<h2>年龄:{{ person.age }}</h2>
<h2>车:{{ person.car.c1 }}、{{ person.car.c2 }}</h2>
<button @click="changeName">修改姓名</button>
<button @click="changeAge">修改年龄</button>
<button @click="changeCar1">修改车1</button>
<button @click="changeCar2">修改车2</button>
<button @click="changeCar">修改车</button>
</div>
</template>

<script>
export default {
name: 'Person',
}
</script>

<script setup>
import { reactive,watch } from 'vue';

//数据
let person = reactive({
name: '张三',
age: 18,
car:{
c1: '宝马',
c2: '奔驰'
}
})
//方法
function changeName() {
person.name += '~'
}
function changeAge() {
person.age += 1
}
function changeCar1() {
person.car.c1 = '奥迪'
}
function changeCar2() {
person.car.c2 = '大众'
}
function changeCar(){
person.car = {c1: '保时捷', c2: '雅迪'}
}

//监视
watch(() => person.name, (newValue, oldValue) => {
console.log('name', newValue, oldValue)
})

//监视属性变化
watch(person.car, (newValue, oldValue) => {
console.log('car', newValue, oldValue)
})

//监视整个变化,加上deep:true即可全部监视
watch(()=>person.car, (newValue, oldValue) => {
console.log('car', newValue, oldValue)
}, {deep: true})

</script>

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

button {
margin: 0 5px;
}
}
</style>
4.2.6.5情况五

监视上述的多个数据

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<template>
<div class="person">
<h2>姓名:{{ person.name }}</h2>
<h2>年龄:{{ person.age }}</h2>
<h2>车:{{ person.car.c1 }}、{{ person.car.c2 }}</h2>
<button @click="changeName">修改姓名</button>
<button @click="changeAge">修改年龄</button>
<button @click="changeCar1">修改车1</button>
<button @click="changeCar2">修改车2</button>
<button @click="changeCar">修改车</button>
</div>
</template>

<script>
export default {
name: 'Person',
}
</script>

<script setup>
import { reactive,watch } from 'vue';

//数据
let person = reactive({
name: '张三',
age: 18,
car:{
c1: '宝马',
c2: '奔驰'
}
})
//方法
function changeName() {
person.name += '~'
}
function changeAge() {
person.age += 1
}
function changeCar1() {
person.car.c1 = '奥迪'
}
function changeCar2() {
person.car.c2 = '大众'
}
function changeCar(){
person.car = {c1: '保时捷', c2: '雅迪'}
}

// //监视
// watch(() => person.name, (newValue, oldValue) => {
// console.log('name', newValue, oldValue)
// })

// //监视属性变化
// watch(person.car, (newValue, oldValue) => {
// console.log('car', newValue, oldValue)
// })

// //监视整个变化,加上deep:true即可全部监视
// watch(()=>person.car, (newValue, oldValue) => {
// console.log('car', newValue, oldValue)
// }, {deep: true})

//监视多个数据
watch([() => person.name, () => person.car.c1], (newValue, oldValue) => {
console.log('name', newValue, oldValue)
}, {deep: true})

</script>

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

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

立即运行一个函数,同时响应式跟踪其依赖,并在依赖更改时重新执行该函数

watch对比watchEffect

  1. 都能监听响应式数据的变化,不同的是监听数据变化的方式不同
  2. watch:要明确指出监视的数据
  3. watchEffect:不用明确指出监视的数据(函数用到哪些属性,那就监视哪些数据)
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
<template>
<div class="person">
<h2>需求:当水温达到60度,或水位达到80cm时,给服务器发送请求</h2>
<h2>当前水温为:{{ tmp }}℃</h2>
<h2>当前水位为:{{ height }}cm</h2>
<button @click="changeSum">水温+10</button>
<button @click="changeHeight">水位+10</button>
</div>
</template>

<script>
export default {
name: 'Person',
}
</script>

<script setup>
import { ref,watch, watchEffect } from 'vue'

//数据
let tmp = ref(0)
let height = ref(0)

//方法
function changeSum() {
tmp.value += 10
}
function changeHeight() {
height.value += 10
}

//监视
// watch([tmp, height], (val) => {
// let [newTmp, newHeight] = val
// if (newTmp >= 60 || newHeight >= 80) {
// console.log('发送请求')
// }
// })

//watchEffect
watchEffect(() => {
// ||导致后面失效
if (tmp.value >= 60 || height.value >= 80) {
console.log('发送请求')
}
})

</script>

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

button {
margin: 0 5px;
}
}
</style>
4.2.8 标签的ref属性

作用:用于注册模板引用

  • 用在普通DOM标签上,获取的是DOM节点
  • 用在组件标签上,获取的是组件实例对象
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">
<h1>中国</h1>
<h2 ref="title2">武汉</h2>
<button @click="showLog">输出h2元素</button>
</div>
</template>

<script setup name="Person">
import { ref,defineExpose } from 'vue'

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

const showLog = () => {
console.log(title2.value)
}

//在组件使用ref,要使其暴露才能看见具体数据
let a = ref(1)
let b = ref(2)
defineExpose({ a, b })

</script>

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

button {
margin: 0 5px;
}
}
</style>
4.2.9 TS
4.2.9.1 基础

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
<template>
<div class="person">

</div>
</template>

<script lang="ts" setup name="Person"> //使用ts必须使用lang="ts"
import { type PersonInter,type Persons } from '@/types' //引用方法
let person:PersonInter = { //接口规范
id: 'asjdijasidja1',
name: 'John Doe',
age: 30
}

let personlist:Persons = [
{
id: 'asjdijasidja1',
name: 'John Doe',
age: 30
},
{
id: 'asjdijasidja2',
name: 'John Doe',
age: 30
}
]

</script>

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

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

ts规定一个规范接口(types/index.ts)

1
2
3
4
5
6
7
8
9
//定义一个接口,用于限制person对象的具体属性,此为分别暴露
export interface PersonInter{
id: string,
name: string,
age: number
}

//一个自定义类型
export type Persons = PersonInter[]
4.2.9.2 props

src/components/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
37
38
39
40
41
42
<template>
<div class="person">
<h2>{{ a }}</h2>
<ul>
<li v-for="item in list" :key="item.id">{{ item.name }}-{{ item.age }}</li> <!-- item为表示,list为需要循环的数据,需要用key为每个数据绑定自己的id加上:才能读取自己的id否则读取都为相同的id -->
</ul>
</div>
</template>

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

//只接收
//defineProps(['a', 'list'])

//限制类型的接收
// defineProps<{list:Persons,a:string}>()

//限制接收+限制必要性+指定默认值,?是限制必要性,withDefaults是指定默认值
withDefaults(defineProps<{list?:Persons,a?:string}>(),{
a:'默认值',
list:()=>[{id:'1',name:'张三',age:18}]
})

// 接收并保存
// let x = defineProps('a')
// console.log(x)

</script>

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

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

src/App.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
<template>
<!-- a为字符串1+1 b为表达式2 c为字符串x d为表达式8 ref不需要: -->
<!-- <h2 a="1+1" :b="1+1" c="x" :d="x" ref="qwe"></h2> -->
<Person a="好好" :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 x = 8

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


</script>
4.2.10 生命周期
4.2.10.1 组件的生命周期

在不同的时间调用特定的函数

创建-挂载-更新-销毁

4.2.10.2 vue2的生命周期

4个阶段8个钩子hooks

创建(创建前beforeCreate,创建完毕created)

挂载(挂载前beforeMount,挂载完毕mounted)

更新(更新前beforeUpdate,更新完毕updated)

销毁(销毁前beforeDestroy,销毁完毕destroy)

src/App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div>
<Person v-if="ifShow"/>
</div>
</template>

<script>
import Person from "./components/Person.vue"
export default {
name: 'App',
data() {
return {
ifShow: true
}
},
components: {
Person
}
}
</script>

src/components/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
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
63
64
65
66
67
68
<template>
<div class="person">
<h2>当前求和为:{{sum}}</h2>
<button @click="increment">+</button>
</div>

</template>

<script>

export default {
// eslint-disable-next-line
name: 'Person',
data() {
return {
sum: 0
}
},
methods: {
increment() {
this.sum++
}
},
//创建前的钩子
beforeCreate() {
console.log('beforeCreate()')
},
//创建后的钩子
created() {
console.log('created()')
},
//挂载前的钩子
beforeMount() {
console.log('beforeMount()')
},
//挂载后的钩子
mounted() {
console.log('mounted()')
},
//更新前的钩子
beforeUpdate() {
console.log('beforeUpdate()')
},
//更新后的钩子
updated() {
console.log('updated()')
},
//销毁前的钩子
beforeDestroy() {
console.log('beforeDestroy()')
},
//销毁后的钩子
destroyed() {
console.log('destroyed()')
}
}
</script>

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

</style>

4.2.10.3 vue3的生命周期

创建-挂载-更新-卸载

创建 setup(执行一次setup里的语句)

挂载 onBeforeMount onMounted

更新 onBeforeUpdate onUpdated

卸载 onBeforeUnmount onUnmounted

常用的钩子:挂载完毕、更新完毕、卸载之前

src/App.vue

1
2
3
4
5
6
7
8
9
10
<template>
<Person v-if="isShow"/>
</template>

<script lang="ts" setup name="App">
import Person from '@/components/Person.vue' //引用组件
import { ref } from 'vue'
let isShow = ref(true)

</script>

src/components/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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<template>
<div class="person">
<h2>当前求和为:{{ sum }}</h2>
<button @click="add">+1</button>
</div>
</template>

<script lang="ts" setup name="Person">
import { ref,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted } from 'vue'
let sum = ref(0)
const add = () => sum.value++

//创建
console.log('创建')
//挂载
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;
border-radius: 10px;
padding: 20px;

button {
margin: 0 5px;
}
}
</style>
4.2.10.4 自定义hooks

src/components/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
<template>
<div class="person">
<h2>当前求和为:{{ sum }}</h2>
<button @click="add">+</button>
<hr>
<img v-for="(dog,index) in dogList" :src="dog" :key="index">
<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 } = useSum()
const { dogList, getDog } = useDog()
</script>

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

button {
margin: 0 5px;
}
img {
height: 100px;
margin-right: 10px;
}
}
</style>

src/hooks/useDog

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 { reactive,onMounted } from 'vue'
import axios from 'axios'

export default function() {
//数据
let dogList = reactive([
'https://images.dog.ceo/breeds/pembroke/n02113023_3885.jpg'
])
//方法
const getDog = async () => {
try {
let res = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')
dogList.push(res.data.message)
} catch (err) {
alert(err)
}
}
//钩子
onMounted(async () => {
await getDog()
})
//向外部提供数据和方法
return {dogList, getDog}
}

src/hooks/useSum

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

export default function () {
//数据
let sum = ref(0)
//方法
const add = () => {
sum.value++
}
//钩子
onMounted(() => {
add()
})
//向外部提供数据和方法
return {sum, add}
}


4.2.11 路由

npm i vue-router

4.2.11.1 基本切换

src/App.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
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
63
64
65
66
<template>
<div class="app">
<h2 class="title">Vue路由测试</h2>
<!-- 导航区 -->
<div class="nav">
<RouterLink to="/home" active-class="ac">首页</RouterLink>
<RouterLink to="/news" active-class="ac">新闻</RouterLink>
<RouterLink to="/about" active-class="ac">关于</RouterLink>
</div>
<!-- 展示区 -->
<div class="main-content">
<RouterView />
</div>
</div>
</template>

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

<style scoped>
.title {
text-align: center;
word-spacing: 5px;
margin: 30px 0;
height: 70px;
line-height: 70px;
background-image: linear-gradient(45deg,gray,white);
border-radius: 10px;
box-shadow: 0 0 2px;
font-size: 30px;
}
.nav{
display: flex;
justify-content: space-around;
margin: 0 100px;
}
.nav a{
display: block;
text-align: center;
width: 90px;
height: 40px;
line-height: 40px;
border-radius: 10px;
background-color: gray;
text-decoration: none;
color: white;
font-size: 18px;
letter-spacing: 5px;
}
.nav a.ac{
background-color: #64967E;
color: #ffc268;
font-weight: 900;
text-shadow: 0 0 1px black;
font-family: 微软雅黑;
}
.main-content{
margin: 0 auto;
margin-top: 30px;
border-radius: 10px;
width: 90%;
height: 400px;
border: 1px solid;
}
</style>

src/router/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
// 创建一个路由器,并暴露出去

// 引入 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

}
]
})

// 暴露出去router
export default router

src/pages/About.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div class="about">
<h2>鱼鱼鱼鱼</h2>
</div>
</template>

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

</script>

<style scoped>
.about {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
color: rgb(85,84,84);
font-size: 18px;
}
</style>

src/pages/News.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
37
38
39
40
41
42
<template>
<div class="news">
<ul>
<li ref="#">新闻1</li>
<li ref="#">新闻2</li>
<li ref="#">新闻3</li>
<li ref="#">新闻4</li>
</ul>
</div>
</template>

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

</script>

<style scoped>
.news {
padding: 0 20px;
display: flex;
justify-content: space-between;
height: 100%;
}
.news ul {
margin-top: 30px;
list-style: none;
padding-left: 10px;
}
.news li>a {
font-size: 18px;
line-height: 18px;
text-decoration: none;
color: #64967E;
text-shadow: 0 0 1px rgb(0,84,0);
}
.news-content {
width: 70%;
height: 90%;
border: 1px solid;
margin-top: 20px;
border-radius: 10px;
}
</style>

src/pages/Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div class="home">
<img src="http://www.atguigu.com/images/index_new/logo.png" alt="logo" />
</div>
</template>

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

</script>

<style scoped>
.home {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
</style>
4.2.11.2 两个注意点
  1. 路由组件通常放在pages或views文件夹中,一般组件通常放在components文件夹。
  2. 通过点击导航,视觉效果上”消失”了的路由组件,默认是被卸载掉的,需要的时候再去挂载

路由组件:靠路由的规则渲染出来的

一般组件:亲手写标签出来的

4.2.11.3 路由的工作模式

history模式

优点:URL更美观,不带有#,更接近传统的网站URL

缺点:后期项目上线,需要服务器配合处理路径问题,否则刷新会有404错误

1
2
3
4
5
const router = createRouter({
// 指定路由的模式
history: createWebHistory(), //history模式
/******/
})

hash模式

优点:兼容性更好,因为不需要服务器端处理路径

缺点:URL带有#不大美观,且在SEO优化方面相对较差

1
2
3
4
5
const router = createRouter({
// 指定路由的模式
history: createWebHashHistory(), //hash模式
/******/
})
4.2.11.4 to的两种写法
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
63
64
65
66
<template>
<div class="app">
<h2 class="title">Vue路由测试</h2>
<!-- 导航区 -->
<div class="nav">
<RouterLink to="/home" active-class="ac">首页</RouterLink> //字符串写法
<RouterLink :to="{path:'/news'}" active-class="ac">新闻</RouterLink> //对象写法
<RouterLink to="/about" active-class="ac">关于</RouterLink>
</div>
<!-- 展示区 -->
<div class="main-content">
<RouterView />
</div>
</div>
</template>

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

<style scoped>
.title {
text-align: center;
word-spacing: 5px;
margin: 30px 0;
height: 70px;
line-height: 70px;
background-image: linear-gradient(45deg,gray,white);
border-radius: 10px;
box-shadow: 0 0 2px;
font-size: 30px;
}
.nav{
display: flex;
justify-content: space-around;
margin: 0 100px;
}
.nav a{
display: block;
text-align: center;
width: 90px;
height: 40px;
line-height: 40px;
border-radius: 10px;
background-color: gray;
text-decoration: none;
color: white;
font-size: 18px;
letter-spacing: 5px;
}
.nav a.ac{
background-color: #64967E;
color: #ffc268;
font-weight: 900;
text-shadow: 0 0 1px black;
font-family: 微软雅黑;
}
.main-content{
margin: 0 auto;
margin-top: 30px;
border-radius: 10px;
width: 90%;
height: 400px;
border: 1px solid;
}
</style>
4.2.11.5 命名路由

src/App.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
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
63
64
65
66
<template>
<div class="app">
<h2 class="title">Vue路由测试</h2>
<!-- 导航区 -->
<div class="nav">
<RouterLink to="/home" active-class="ac">首页</RouterLink>
<RouterLink :to="{path:'/news'}" active-class="ac">新闻</RouterLink>
<RouterLink :to="{name: 'guanyu'}" active-class="ac">关于</RouterLink>
</div>
<!-- 展示区 -->
<div class="main-content">
<RouterView />
</div>
</div>
</template>

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

<style scoped>
.title {
text-align: center;
word-spacing: 5px;
margin: 30px 0;
height: 70px;
line-height: 70px;
background-image: linear-gradient(45deg,gray,white);
border-radius: 10px;
box-shadow: 0 0 2px;
font-size: 30px;
}
.nav{
display: flex;
justify-content: space-around;
margin: 0 100px;
}
.nav a{
display: block;
text-align: center;
width: 90px;
height: 40px;
line-height: 40px;
border-radius: 10px;
background-color: gray;
text-decoration: none;
color: white;
font-size: 18px;
letter-spacing: 5px;
}
.nav a.ac{
background-color: #64967E;
color: #ffc268;
font-weight: 900;
text-shadow: 0 0 1px black;
font-family: 微软雅黑;
}
.main-content{
margin: 0 auto;
margin-top: 30px;
border-radius: 10px;
width: 90%;
height: 400px;
border: 1px solid;
}
</style>

src/router/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
// 创建一个路由器,并暴露出去

// 引入 createRouter
import { createRouter,createWebHistory } from 'vue-router'
// 引入路由配置
import Home from '@/pages/Home.vue'
import News from '@/pages/News.vue'
import About from '@/pages/About.vue'

// 创建一个路由器
const router = createRouter({
// 指定路由的模式
history: createWebHistory(),
// 配置路由
routes: [
{
name: 'zhuye',
path: '/home',
component:Home
},
{
name: 'xinwen',
path: '/news',
component:News
},
{
name: 'guanyu',
path: '/about',
component:About

}
]
})

// 暴露出去router
export default router
4.2.11.6 嵌套路由

src/pages/detail.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<ul class="news-list">
<li>编号:</li>
<li>标题:</li>
<li>内容:</li>
</ul>
</template>

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

</script>

<style scoped>
.news-list {
list-style: none;
padding-left: 20px;
}
.news-list>li {
line-height: 30px;
}
</style>

src/router/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
43
// 创建一个路由器,并暴露出去

// 引入 createRouter
import { createRouter,createWebHistory } from 'vue-router'
// 引入路由配置
import Home from '@/pages/Home.vue'
import News from '@/pages/News.vue'
import About from '@/pages/About.vue'
import Detail from '@/pages/Detail.vue'

// 创建一个路由器
const router = createRouter({
// 指定路由的模式
history: createWebHistory(),
// 配置路由
routes: [
{
name: 'zhuye',
path: '/home',
component:Home
},
{
name: 'xinwen',
path: '/news',
component:News,
children:[
{
path: 'detail',
component:Detail
}
]
},
{
name: 'guanyu',
path: '/about',
component:About

}
]
})

// 暴露出去router
export default router
4.2.11.7 路由参数
  1. query参数

src/pages/detail.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
<template>
<ul class="news-list">
<li>编号:{{ query.id }}</li>
<li>标题:{{ query.title }}</li>
<li>内容:{{ query.content }}</li>
</ul>
</template>

<script setup lang="ts" name="Detail">
import { toRefs } from 'vue';
import { useRoute } from 'vue-router';
const route = useRoute();
const {query} = toRefs(route);
</script>

<style scoped>
.news-list {
list-style: none;
padding-left: 20px;
}
.news-list>li {
line-height: 30px;
}
</style>

src/pages/News.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
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<template>
<div class="news">
<!-- 导航区 -->
<ul>
<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>
</ul>
<!-- 展示区 -->
<div class="news-content">
<RouterView />

</div>
</div>
</template>

<script lang="ts" setup name="News">
import { onMounted,onUnmounted,reactive } from 'vue'
import { RouterView,RouterLink } from 'vue-router'
onMounted(() => {
console.log('News mounted')
})
onUnmounted(() => {
console.log('News unmounted')
})
const newsList = reactive([
{id: 1, title: '新闻1', content: '新闻1的内容'},
{id: 2, title: '新闻2', content: '新闻2的内容'},
{id: 3, title: '新闻3', content: '新闻3的内容'},
{id: 4, title: '新闻4', content: '新闻4的内容'},
])
</script>

<style scoped>
.news {
padding: 0 20px;
display: flex;
justify-content: space-between;
height: 100%;
}
.news ul {
margin-top: 30px;
/* list-style: none; */
padding-left: 10px;
}
.news li::marker {
color: #64967E;
}
.news li>a {
font-size: 18px;
line-height: 18px;
text-decoration: none;
color: #64967E;
text-shadow: 0 0 1px rgb(0,84,0);
}
.news-content {
width: 70%;
height: 90%;
border: 1px solid;
margin-top: 20px;
border-radius: 10px;
}
</style>
  1. params参数

src/pages/detail.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<ul class="news-list">
<li>编号:{{ route.params.id }}</li>
<li>标题:{{ route.params.title }}</li>
<li>内容:{{ route.params.content }}</li>
</ul>
</template>

<script setup lang="ts" name="Detail">
import { useRoute } from 'vue-router';
const route = useRoute();
</script>

<style scoped>
.news-list {
list-style: none;
padding-left: 20px;
}
.news-list>li {
line-height: 30px;
}
</style>

src/pages/News.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
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<template>
<div class="news">
<!-- 导航区 -->
<ul>
<li v-for="news in newsList" :key=news.id>
<!-- 第一种 -->
<RouterLink :to="`/news/detail/${news.id}/${news.title}/${news.content}`">{{ news.title }}</RouterLink>
<!-- 第二种 -->
<RouterLink
:to="{
name:'xiangxi',
params:{
id: news.id,
title: news.title,
content: news.content
}
}"
>
{{ news.title }}
</RouterLink>
</li>
</ul>
<!-- 展示区 -->
<div class="news-content">
<RouterView />

</div>
</div>
</template>

<script lang="ts" setup name="News">
import { onMounted,onUnmounted,reactive } from 'vue'
import { RouterView,RouterLink } from 'vue-router'
onMounted(() => {
console.log('News mounted')
})
onUnmounted(() => {
console.log('News unmounted')
})
const newsList = reactive([
{id: 1, title: '新闻1', content: '新闻1的内容'},
{id: 2, title: '新闻2', content: '新闻2的内容'},
{id: 3, title: '新闻3', content: '新闻3的内容'},
{id: 4, title: '新闻4', content: '新闻4的内容'},
])
</script>

<style scoped>
.news {
padding: 0 20px;
display: flex;
justify-content: space-between;
height: 100%;
}
.news ul {
margin-top: 30px;
/* list-style: none; */
padding-left: 10px;
}
.news li::marker {
color: #64967E;
}
.news li>a {
font-size: 18px;
line-height: 18px;
text-decoration: none;
color: #64967E;
text-shadow: 0 0 1px rgb(0,84,0);
}
.news-content {
width: 70%;
height: 90%;
border: 1px solid;
margin-top: 20px;
border-radius: 10px;
}
</style>

src/router/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
43
44
// 创建一个路由器,并暴露出去

// 引入 createRouter
import { createRouter,createWebHistory } from 'vue-router'
// 引入路由配置
import Home from '@/pages/Home.vue'
import News from '@/pages/News.vue'
import About from '@/pages/About.vue'
import Detail from '@/pages/Detail.vue'

// 创建一个路由器
const router = createRouter({
// 指定路由的模式
history: createWebHistory(),
// 配置路由
routes: [
{
name: 'zhuye',
path: '/home',
component:Home
},
{
name: 'xinwen',
path: '/news',
component:News,
children:[
{
name: 'xiangxi',
path: 'detail/:id/:title/:content?', // ?表示content可传可不传
component:Detail
}
]
},
{
name: 'guanyu',
path: '/about',
component:About

}
]
})

// 暴露出去router
export default router
4.2.11.8 路由props配置

src/pages/detail.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<ul class="news-list">
<li>编号:{{ id }}</li>
<li>标题:{{ title }}</li>
<li>内容:{{ content }}</li>
</ul>
</template>

<script setup lang="ts" name="Detail">
defineProps(['id', 'title', 'content']);
</script>

<style scoped>
.news-list {
list-style: none;
padding-left: 20px;
}
.news-list>li {
line-height: 30px;
}
</style>

src/router/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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// 创建一个路由器,并暴露出去

// 引入 createRouter
import { createRouter,createWebHistory } from 'vue-router'
// 引入路由配置
import Home from '@/pages/Home.vue'
import News from '@/pages/News.vue'
import About from '@/pages/About.vue'
import Detail from '@/pages/Detail.vue'

// 创建一个路由器
const router = createRouter({
// 指定路由的模式
history: createWebHistory(),
// 配置路由
routes: [
{
name: 'zhuye',
path: '/home',
component:Home
},
{
name: 'xinwen',
path: '/news',
component:News,
children:[
{
name: 'xiangxi',
path: 'detail/:id/:title/:content?', // ?表示content可传可不传
component:Detail,
// 第一种写法,将路由收到的所有params参数作为props传递给路由组件
props:true
// 第二种写法函数写法,可以自己决定将什么作为props给路由组件
// props(route){
// return route.query
// }
// 第三种写法对象写法,可以自己决定将什么作为props给路由组件
// props:{
// id:1,
// title:'标题',
// content:'内容'
// }
}
]
},
{
name: 'guanyu',
path: '/about',
component:About

}
]
})

// 暴露出去router
export default router
4.2.11.9 路由replace属性

路由一般为push模式,即可以回退;replace后即为直接覆盖,不可回退

src/App.vue

加入replace即可

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
63
64
65
66
<template>
<div class="app">
<h2 class="title">Vue路由测试</h2>
<!-- 导航区 -->
<div class="nav">
<RouterLink replace to="/home" active-class="ac">首页</RouterLink>
<RouterLink replace :to="{path:'/news'}" active-class="ac">新闻</RouterLink>
<RouterLink replace :to="{name: 'guanyu'}" active-class="ac">关于</RouterLink>
</div>
<!-- 展示区 -->
<div class="main-content">
<RouterView />
</div>
</div>
</template>

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

<style scoped>
.title {
text-align: center;
word-spacing: 5px;
margin: 30px 0;
height: 70px;
line-height: 70px;
background-image: linear-gradient(45deg,gray,white);
border-radius: 10px;
box-shadow: 0 0 2px;
font-size: 30px;
}
.nav{
display: flex;
justify-content: space-around;
margin: 0 100px;
}
.nav a{
display: block;
text-align: center;
width: 90px;
height: 40px;
line-height: 40px;
border-radius: 10px;
background-color: gray;
text-decoration: none;
color: white;
font-size: 18px;
letter-spacing: 5px;
}
.nav a.ac{
background-color: #64967E;
color: #ffc268;
font-weight: 900;
text-shadow: 0 0 1px black;
font-family: 微软雅黑;
}
.main-content{
margin: 0 auto;
margin-top: 30px;
border-radius: 10px;
width: 90%;
height: 400px;
border: 1px solid;
}
</style>
4.2.11.10 编程式路由导航

脱离实现路由跳转

src/pages/News.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
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
<template>
<div class="news">
<!-- 导航区 -->
<ul>
<li v-for="news in newsList" :key=news.id>
<button @click="showNewsDtail(news)">查看新闻</button>
<RouterLink
:to="{
name:'xiangxi',
params:{
id: news.id,
title: news.title,
content: news.content
}
}"
>
{{ news.title }}
</RouterLink>
</li>
</ul>
<!-- 展示区 -->
<div class="news-content">
<RouterView />

</div>
</div>
</template>

<script lang="ts" setup name="News">
import { reactive } from 'vue'
import { RouterView,RouterLink,useRouter } from 'vue-router'
const newsList = reactive([
{id: 1, title: '新闻1', content: '新闻1的内容'},
{id: 2, title: '新闻2', content: '新闻2的内容'},
{id: 3, title: '新闻3', content: '新闻3的内容'},
{id: 4, title: '新闻4', content: '新闻4的内容'},
])

const router = useRouter()

interface NewsInter{
id: number,
title: string,
content: string
}

const showNewsDtail = (news:NewsInter) => {
router.push({ //push replace两种方式
name:'xiangxi',
params:{
id: news.id,
title: news.title,
content: news.content
}
})
}

</script>

<style scoped>
.news {
padding: 0 20px;
display: flex;
justify-content: space-between;
height: 100%;
}
.news ul {
margin-top: 30px;
/* list-style: none; */
padding-left: 10px;
}
.news li::marker {
color: #64967E;
}
.news li>a {
font-size: 18px;
line-height: 18px;
text-decoration: none;
color: #64967E;
text-shadow: 0 0 1px rgb(0,84,0);
}
.news-content {
width: 70%;
height: 90%;
border: 1px solid;
margin-top: 20px;
border-radius: 10px;
}
</style>

场景:1.符合某些条件跳转 2.鼠标滑过一个东西跳转

4.2.11.11 路由重定向

src/router/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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 创建一个路由器,并暴露出去

// 引入 createRouter
import { createRouter,createWebHistory } from 'vue-router'
// 引入路由配置
import Home from '@/pages/Home.vue'
import News from '@/pages/News.vue'
import About from '@/pages/About.vue'
import Detail from '@/pages/Detail.vue'

// 创建一个路由器
const router = createRouter({
// 指定路由的模式
history: createWebHistory(),
// 配置路由
routes: [
{
name: 'zhuye',
path: '/home',
component:Home
},
{
name: 'xinwen',
path: '/news',
component:News,
children:[
{
name: 'xiangxi',
path: 'detail/:id/:title/:content?', // ?表示content可传可不传
component:Detail,
// 第一种写法,将路由收到的所有params参数作为props传递给路由组件
props:true
// 第二种写法函数写法,可以自己决定将什么作为props给路由组件
// props(route){
// return route.query
// }
// 第三种写法对象写法,可以自己决定将什么作为props给路由组件
// props:{
// id:1,
// title:'标题',
// content:'内容'
// }
},
{
path: '',
redirect:'/news/detail/1/新闻1/新闻1的内容'
}
]
},
{
name: 'guanyu',
path: '/about',
component:About

},
{
path: '/',
redirect: '/home'
}
]
})

// 暴露出去router
export default router
4.2.12 Pinia

生成id

npm i nanoid

npm i uuid

集中式状态(数据)管理

4.2.12.1 准备

src/components/Count.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
37
38
<template>
<div class="count">
<h2>当前求和:{{ sum }}</h2>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="add">+</button>
<button @click="minus">-</button>
</div>
</template>

<script lang="ts" setup name="Count">
import { ref } from 'vue';
let sum = ref(1); //和
let n = ref(1); //用户选中数字
const add = () => {
sum.value += n.value;
};
const minus = () => {
sum.value -= n.value;
};
</script>


<style scoped>
.count {
background-color: skyblue;
padding: 10px;
border-radius: 10px;
box-shadow: 0 0 10px;
}
.selected {
margin: 0 5px;
height: 25px;
}
</style>

src/components/LoveTalk.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
37
38
39
<template>
<div class="talk">
<button @click="getLoveTalk">获取土味情话</button>
<ul>
<li v-for="talk in talkList" :key="talk.id">{{ talk.content }}</li>
</ul>
</div>
</template>

<script lang="ts" setup name="LoveTalk">
import { reactive } from 'vue'
import axios from 'axios'
import { nanoid } from 'nanoid';

let talkList = reactive([
{ id: '1', content: '“你最近是不是又胖了?” “没有啊,为什么这么说?” “那为什么在我心里的分量越来越重了?”' },
{ id: '2', content: '我觉得你这个人不适合谈恋爱” “为什么?” “适合结婚。”' },
{ id: '3', content: '最近有谣言说我喜欢你,我要澄清一下,那不是谣言。' }
])

const getLoveTalk = async() => {
//发送请求 下面这行的写法是连续结构赋值加重命名
let {data:{content:content}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
//把请求回来的字符串,包装成一个对象
let obj = {id:nanoid(),content} //属性名相同可以不写:content
//把这个对象添加到talkList中
talkList.unshift(obj)
}

</script>

<style scoped>
.talk {
background-color: pink;
padding: 10px;
border-radius: 10px;
box-shadow: 0 0 10px;
}
</style>

src/App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div>
<Count />
<hr>
<LoveTalk/>
</div>
</template>

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

<style scoped>

</style>
4.2.12.2 搭建pinia环境

npm i pinia

src/main.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { createApp } from "vue";
import App from "./App.vue";
//1.引入pinia
import { createPinia } from "pinia";

const app = createApp(App)

//2.创建pinia
const pinia = createPinia()

//3.安装pinia
app.use(pinia)

app.mount("#app")

4.2.12.3 存储+读取数据

reactive里的ref会自动拆包,不用.value

src/store/count.ts

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

export const useCountStore = defineStore('count',{
//真正存储数据的地方
state(){
return{
sum:1
}
}
})

src/store/loveTalk.ts

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

export const useTalkStore = defineStore('talk', {
//真正存储数据的地方
state() {
return {
talkList: [
{ id: '1', content: '“你最近是不是又胖了?” “没有啊,为什么这么说?” “那为什么在我心里的分量越来越重了?”' },
{ id: '2', content: '我觉得你这个人不适合谈恋爱” “为什么?” “适合结婚。”' },
{ id: '3', content: '最近有谣言说我喜欢你,我要澄清一下,那不是谣言。' }
]
}
}
})
4.2.12.4 修改数据的三种方式

src/count.ts

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里面放置的是一个一个的方法,用来修改state里面的数据
actions:{
increment(val:number){
// this指向的是当前的store
this.sum += val
}
},
//真正存储数据的地方
state(){
return{
sum:1,
school:'hbu',
address:'wuhan'
}
}
})

src/components/Count.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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<template>
<div class="count">
<h2>当前求和:{{ countStore.sum }}</h2>
<h3>欢迎来到:{{ countStore.school }},坐落于:{{ countStore.address }}</h3>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="add">+</button>
<button @click="minus">-</button>
</div>
</template>

<script lang="ts" setup name="Count">
import { ref } from 'vue';
import { useCountStore} from '@/store/count'

const countStore = useCountStore()

//以下两种方法都可以拿到state数据
// countStore.sum
// countStore.$state.sum
let n = ref(1); //用户选中数字
const add = () => {
// 第一种
//countStore.sum += n.value;
// 第二种
// countStore.$patch((state) => {
// state.sum += n.value,
// state.school = '清华大学',
// state.address = '北京'
// });
// 第三种
countStore.increment(n.value)

};
const minus = () => {
countStore.sum -= n.value;
};
</script>


<style scoped>
.count {
background-color: skyblue;
padding: 10px;
border-radius: 10px;
box-shadow: 0 0 10px;
}
.selected {
margin: 0 5px;
height: 25px;
}
</style>
4.2.12.5 storeToRefs

src/components/Count.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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<template>
<div class="count">
<h2>当前求和:{{ sum }}</h2>
<h3>欢迎来到:{{ school }},坐落于:{{ address }}</h3>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="add">+</button>
<button @click="minus">-</button>
</div>
</template>

<script lang="ts" setup name="Count">
import { ref } from 'vue';
import { useCountStore} from '@/store/count'
import { storeToRefs } from 'pinia';

const countStore = useCountStore()
//storeToRefs只会关注数据,不会对方法进行ref
const {sum, school, address}= storeToRefs(countStore)

//以下两种方法都可以拿到state数据
// countStore.sum
// countStore.$state.sum
let n = ref(1); //用户选中数字
const add = () => {
// 第一种
//countStore.sum += n.value;
// 第二种
// countStore.$patch((state) => {
// state.sum += n.value,
// state.school = '清华大学',
// state.address = '北京'
// });
// 第三种
countStore.increment(n.value)

};
const minus = () => {
countStore.sum -= n.value;
};
</script>


<style scoped>
.count {
background-color: skyblue;
padding: 10px;
border-radius: 10px;
box-shadow: 0 0 10px;
}
.selected {
margin: 0 5px;
height: 25px;
}
</style>
4.2.12.6 getters

src/store/count.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
import { defineStore } from "pinia"

export const useCountStore = defineStore('count',{
//actions里面放置的是一个一个的方法,用来修改state里面的数据
actions:{
increment(val:number){
// this指向的是当前的store
this.sum += val
}
},
//真正存储数据的地方
state(){
return{
sum:1,
school:'hbu',
address:'wuhan'
}
},
getters:{
bigSum:state=>state.sum*10, //箭头函数会导致this丢失
upperSchool():string{
return this.school.toUpperCase()
}
}
})

src/components/Count.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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<template>
<div class="count">
<h2>当前求和:{{ sum }},放大十倍后:{{ bigSum }}</h2>
<h3>欢迎来到:{{ school }},坐落于:{{ address }},大写:{{ upperSchool }}</h3>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="add">+</button>
<button @click="minus">-</button>
</div>
</template>

<script lang="ts" setup name="Count">
import { ref } from 'vue';
import { useCountStore} from '@/store/count'
import { storeToRefs } from 'pinia';

const countStore = useCountStore()
//storeToRefs只会关注数据,不会对方法进行ref
const {sum, school, address,bigSum,upperSchool}= storeToRefs(countStore)

//以下两种方法都可以拿到state数据
// countStore.sum
// countStore.$state.sum
let n = ref(1); //用户选中数字
const add = () => {
// 第一种
//countStore.sum += n.value;
// 第二种
// countStore.$patch((state) => {
// state.sum += n.value,
// state.school = '清华大学',
// state.address = '北京'
// });
// 第三种
countStore.increment(n.value)

};
const minus = () => {
countStore.sum -= n.value;
};
</script>


<style scoped>
.count {
background-color: skyblue;
padding: 10px;
border-radius: 10px;
box-shadow: 0 0 10px;
}
.selected {
margin: 0 5px;
height: 25px;
}
</style>
4.2.12.7 $subscribe

src/store/loveTalk.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { defineStore } from "pinia"
import axios from "axios"
import { nanoid } from "nanoid"

export const useTalkStore = defineStore('talk', {
actions: {
async getATalk() {
//发送请求 下面这行的写法是连续结构赋值加重命名
let { data: { content: content } } = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
//把请求回来的字符串,包装成一个对象
let obj = { id: nanoid(), content } //属性名相同可以不写:content
//把这个对象添加到talkList中
this.talkList.unshift(obj)
}
},
//真正存储数据的地方
state() {
return {
talkList:JSON.parse(localStorage.getItem('talkList') as string) || [] //取出浏览器保存的本地数据没有返回[]
}
}
})

src/components/LoveTalk.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
<template>
<div class="talk">
<button @click="getLoveTalk">获取土味情话</button>
<ul>
<li v-for="talk in talkList" :key="talk.id">{{ talk.content }}</li>
</ul>
</div>
</template>

<script lang="ts" setup name="LoveTalk">
import { useTalkStore } from '@/store/loveTalk'
import { storeToRefs } from 'pinia';

const talkStore = useTalkStore()
const { talkList } = storeToRefs(talkStore)

talkStore.$subscribe((mutation, state) => {
console.log(mutation, state)
localStorage.setItem('talkList', JSON.stringify(state.talkList)) //将数据存储到浏览器本地
})

const getLoveTalk = () => {
talkStore.getATalk()
}

</script>

<style scoped>
.talk {
background-color: pink;
padding: 10px;
border-radius: 10px;
box-shadow: 0 0 10px;
}
</style>
4.2.12.8 store组合式写法

src/store/loveTalk.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
43
import { defineStore } from "pinia"
import axios from "axios"
import { nanoid } from "nanoid"

// export const useTalkStore = defineStore('talk', {
// actions: {
// async getATalk() {
// //发送请求 下面这行的写法是连续结构赋值加重命名
// let { data: { content: content } } = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
// //把请求回来的字符串,包装成一个对象
// let obj = { id: nanoid(), content } //属性名相同可以不写:content
// //把这个对象添加到talkList中
// this.talkList.unshift(obj)
// }
// },
// //真正存储数据的地方
// state() {
// return {
// talkList:JSON.parse(localStorage.getItem('talkList') as string) || [] //取出浏览器保存的本地数据没有返回[]
// }
// }
// })
import { reactive, ref } from 'vue'

export const useTalkStore = defineStore('talk', () => {
// talkList相当于state
const talkList = reactive(
JSON.parse(localStorage.getItem('talkList') as string) || []
)
//getATalk相当于action
async function getATalk() {
//发送请求 下面这行的写法是连续结构赋值加重命名
let { data: { content: content } } = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
//把请求回来的字符串,包装成一个对象
let obj = { id: nanoid(), content } //属性名相同可以不写:content
//把这个对象添加到talkList中
talkList.unshift(obj)
}
return {
talkList,
getATalk
}
})
4.2.13 组件通信
4.2.13.1 方式一props

可父传子也可子传父