文章目录
- day 1
- 1. 创建vue3工程
- 3. 响应式数据
- 4. 计算属性
- day 2
- 5. watch 监视
- 6. watchEffect
- 7. 标签的`ref`属性
- day 3
- 8. 回顾TS中的接口_泛型_自定义类型
- 9. `props`的使用
- 10. 生命周期
- 11. 自定义Hooks
- 12. 路由 基本切换效果
- 13. 路由 两个注意点
- 14. 路由 路由器的工作模式
- 15. 路由 to的两种写法
- 16. 路由 命名路由
- 17. 路由 嵌套路由
- 18. 路由 query 参数
- 19. 路由 params 参数
- 20. 路由 props配置
- 21. 路由 replace属性
- 22. 路由 编程式路由导航
- 23. 路由 重定向
- day 4
- 24. pinia 选项式写法
- 25. pinia 组合式写法
- day 5
- 26. 组件通信_props
- 27. 组件通信_custom-event
- 28. 组件通信_mitt
- 29. 组件通信_v-model
- 30. 组件通信_`$attrs`
- 31. 组件通信_`$refs`与`$parent`
- 32. 组件通信_provide-inject
- 32. 组件通信_slot默认插槽
- 33. 组件通信_slot具名插槽
- 34. 组件通信_slot作用域插槽
- 35. shallowRef 和 shallowReactive
- 36. readonly 和 shallowReadonly
- 37. toRaw 和 markRaw
- 38. customRef
- 39. Teleport
- 40. Suspense
- 41. 全局api转化为应用对象
- 42. 其他
day 1
1. 创建vue3工程
相关代码如下:
## 创建vue工程
npm create vue@lastest
## 安装node_modules
npm install // npm i
创建工程完毕之后,进入文件夹可以发现有如下文件,下图是文件介绍:
入口文件介绍:
这里main.ts
是与index.html
建立联系的;
通过.mount('#app')
这样 main.ts
就与index.html
建立起了联系;
其中main.ts
的代码格式如下:
<template>
<div>
<h1>你好</h1>
</div>
</template>
<script setup lang="ts">
// js或者ts
</script>
<style scoped>
/* 样式 */
</style>
App.vue
的代码格式如下:
<template>
<div class="app">
<h1>你好</h1>
</div>
</template>
<script setup lang="ts">
// js或者ts
</script>
<style scoped>
/* 样式 */
.app {
background-color: #ddd;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
</style>
网页调试vue可以在chrome应用商店search vue.js devtools
下载安装就好;
你好
感觉vue2和vue3的主要不同就在于vue文件中script部分;其中vue2是选项式OptionsAPI
的,vue3是组合式Composition
的;
选项式API相关代码如下:
注意,这里并没有setup
;
<script lang="ts">
export default {
// 定义组件名字
name: 'Person',
// 定义数据
data(){
return {
name: '张三',
age: 18,
tel: '138888888'
}
},
// 定义方法
methods:{
changeName(){
this.name = 'zhang-san'
},
changeAge(){
this.age += 1
},
showTel(){
alert(this.tel)
}
},
}
</script>
组合式相关代码如下:
注意,这里的setup比beforecreate执行得还要早,setup的返回值也可以是渲染函数,data,method,setup可以同时存在,setup不可以读取data中的数据,反之则可以,因为setup是执行得最早的;
<script lang="ts">
export default {
name: 'Person',
setup(){
// 数据
let name = '张三' // 此时name,age,tel 不是响应式的 应该改为ref或者reactive
let age = 18 // 此时name,age,tel 不是响应式的 应该改为ref或者reactive
let tel = '138888888' // 此时name,age,tel 不是响应式的 应该改为ref或者reactive
// 方法
function changeName(){
name = 'zhang-san'
}
function changeAge(){
age += 1
}
function showTel(){
alert(tel)
}
// 把数据和方法交出去
return {name, age, changeAge, changeName, showTel}
}
}
</script>
setup语法糖简化后代码如下:
<script lang="ts">
export default {
name: 'Person',
}
</script>
<script setup lang="ts">
// 数据
let name = '张三' // 此时name,age,tel 不是响应式的 应该改为ref或者reactive
let age = 18 // 此时name,age,tel 不是响应式的 应该改为ref或者reactive
let tel = '138888888' // 此时name,age,tel 不是响应式的 应该改为ref或者reactive
// 方法
function changeName(){
name = 'zhang-san'
}
function changeAge(){
age += 1
}
function showTel(){
alert(tel)
}
// 此时不需要return 自动提交
</script>
3. 响应式数据
响应式数据创建有两种方法,一种是ref
,一种是reactive
ref
定义基本类型的数据
使用的相关代码如下:
<template>
<div class="app">
<h2>姓名:{{ name }}</h2> // 利用大括号包起来的是不需要.value的
<h2>年龄:{{ age }}</h2> // 利用大括号包起来的是不需要.value的
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="showTel">查看联系方式</button>
</div>
</template>
<script setup lang="ts">
import {ref} from 'vue'
let name = ref('张三')
let age = ref(18)
let tel = ref('138888888')
// 在ref包裹住后,需要操作值都需要.value
function changeName(){
name.value = 'zhang-san'
}
function changeAge(){
age.value += 1
}
function showTel(){
alert(tel.value)
}
这里值用ref
包裹起来之后,变量会变成一个RefImpl
类的数据,这时要修改值直接对变量操作是无意义的,我们需要对.value
进行操作;这里要注意的是,ref
也可以定义对象类的响应式数据,实现原理是先用ref
包裹,再用reactive
包裹,即取值还是需要用value
;
reactive
定义对象类型的数据
使用的相关代码如下:
<template>
<div class="app">
<h2>姓名:{{ student.name }}</h2>
<h2>年龄:{{ student.age }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
</div>
</template>
<script lang="ts">
export default {
name: 'Person',
}
</script>
<script setup lang="ts">
import {reactive} from 'vue'
let student = reactive({name:'张三', age:18})
function changeName(){
student.name = 'zhang-san'
}
function changeAge(){
student.age += 1
}
</script>
<style scoped>
/* 样式 */
.app {
background-color: #ddd;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button {
margin: 10px;
}
</style>
用reactive
包起来后,对象变成了一个Proxy
的对象,原对象在Proxy
里面的target
中;这里的reactive
是深层次的,只能定义对象类型的响应式数据;
ref
和reactive
的对比
这里要注意的是,重新分配reactive
的对象时,使用Object.assign
,但是如果是利用ref
定义的对象类数据,我们是可以直接进行替换的;
对响应式数据进行解构,toRef
和toRefs
:
let person = reactive({
name: '张三',
age: 18
})
let {name, age} = person
// let name = person.name
// let age = person.age
let {name, age} = toRefs(person) //把reactive对象里面的每一组对象转化为ref
// let name = toRef(person, 'name')
// let age = toRef(person, 'age')
4. 计算属性
v-bind
是 单向绑定,数据流向页面;v-model
是 双向绑定,页面也可以流向数据;
计算属性 computed
是只要响应式变量出现了变化,就随之变化;
使用例子如下:
<template>
<div class="app">
姓名:<input type="text" v-model="name"> <br>
年龄:<input type="text" v-model="age"> <br>
information: {{ information }}
</div>
</template>
<script lang="ts">
export default {
name: 'Person',
}
</script>
<script setup lang="ts">
import {ref, computed} from 'vue'
let name = ref('zhangsan')
let age = ref(18)
let information = computed(()=>{
return name.value + age.value
})
</script>
<style scoped>
/* 样式 */
.app {
background-color: #ddd;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
</style>
computed
是用来区别于方法的,使用computed
得到的属性是没有缓存的,而且computed
得到的属性是不可修改的;如果需要修改,则需要使用以下方法:
<template>
<div class="app">
姓名:<input type="text" v-model="name"> <br>
年龄:<input type="text" v-model="age"> <br>
information: {{ information }} <br>
<button @click="changeInformation">修改information为lisi</button>
</div>
</template>
<script lang="ts">
export default {
name: 'Person',
}
</script>
<script setup lang="ts">
import {ref, computed} from 'vue'
let name = ref('zhangsan')
let age = ref(18)
let information = computed({
get(){
return name.value + '-' + age.value
},
set(val){
const [s1, s2] = val.split('-')
name.value = s1
age.value = parseInt(s2)
}
})
function changeInformation(){
information.value = 'lisi-18'
}
</script>
<style scoped>
/* 样式 */
.app {
background-color: #ddd;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
</style>
day 2
5. watch 监视
情况1:监视ref
定义的基本类型数据
停止监视只需要调用watch
函数的返回值就可以;
<template>
<div>
<h5>情况1:监视【ref】定义的值是【基本类型】的数据</h5>
sum : {{ sum }} <br>
<button @click="changeSum"> sum + 1</button> <br>
<hr>
</div>
</template>
<script lang="ts">
export default {
name: 'Person'
}
</script>
<script setup lang="ts">
import {ref, watch} from 'vue'
let sum = ref(1)
function changeSum(){
sum.value += 1
}
// 这里的sum是不需要添加.value的,返回值就是停止监视的函数
const stopwatch = watch(sum, (newVal, oldVal)=>{
console.log('sum变化了', newVal, oldVal)
// 如果sum的最新值大于等于10则停止监视
if(newVal >= 10 ){
stopwatch()
}
})
</script>
<style scoped>
</style>
情况2:监视ref
定义的对象类型数据
这里要注意的是:若修改的是ref定义的对象中的属性,newVal和oldVal都是新值,因为是同一个对象;若修改的是ref定义的整个对象,newVal是新值,oldVal是旧值,因为不是同一个对象;
<template>
<div>
<h5>情况2:监视【ref】定义的值是【对象类型】的数据</h5>
姓名: {{ person.name }} <br>
年龄: {{ person.age }} <br>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="changeFull">修改整个人</button>
</div>
</template>
<script lang="ts">
export default {
name: 'Person'
}
</script>
<script setup lang="ts">
import {ref, watch} from 'vue'
let person = ref({
name: '张三',
age: 18
})
function changeName (){
person.value.name = '李四'
}
function changeAge (){
person.value.age = 19
}
function changeFull (){
person.value = {name: '王五', age: 20}
}
// 监视【ref】定义的【对象类型数据】,监视的是对象的地址值,若想要监视
// 对象的内部属性的变化,需要手动开启深度监视deep
// 若修改的是ref定义的对象中的属性,newVal和oldVal都是新值,因为是同一个对象
// 若修改的是ref定义的整个对象,newVal是新值,oldVal是旧值,因为不是同一个对象
// watch的第一个参数是:被监视的数据
// watch的第二个参数是:监视的回调
// watch的第三个参数是:配置对象(deep, immediate ... )
watch(person, (newVal, oldVal)=>{
console.log('person变化了', newVal, oldVal)
}, {deep: true})
</script>
<style scoped>
button {
margin: 10px;
}
</style>
情况3:监视reactive
定义的对象类型数据
很简单,只需要把ref
定义的对象改为reactive
定义的对象,然后在修改整个对象的时候使用Object.assign
替换就可以;
<template>
<div>
<h5>情况3:监视【reactive】定义的值是【对象类型】的数据</h5>
姓名: {{ person.name }} <br>
年龄: {{ person.age }} <br>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="changeFull">修改人</button>
</div>
</template>
<script lang="ts">
export default {
name: 'Person'
}
</script>
<script setup lang="ts">
import {reactive, watch} from 'vue'
let person = reactive({
name: '张三',
age: 18
})
function changeName (){
person.name = '李四'
}
function changeAge (){
person.age = 19
}
function changeFull (){
// 并没有修改地址值;
Object.assign(person, {name: '王五', age: 20})
}
// 监视【reactive】定义的对象类型数据,默认是开启深度监视的,而且深度是关不掉的
watch(person, (newVal, oldVal)=>{
console.log('person变化了', newVal, oldVal)
})
</script>
<style scoped>
button {
margin: 10px;
}
</style>
情况4:监视ref
或者reactive
定义的对象类型中的某个属性
<template>
<div>
<h5>情况4:监视【ref或reactive】定义的【对象类型】某个属性</h5>
姓名: {{ person.name }} <br>
年龄: {{ person.age }} <br>
车辆:{{ person.car.c1 }}, {{ person.car.c2 }} <br>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="changeC1">修改c1</button>
<button @click="changeC2">修改c2</button>
<button @click="changeCar">修改car</button>
</div>
</template>
<script lang="ts">
export default {
name: 'Person'
}
</script>
<script setup lang="ts">
import {reactive, watch} from 'vue'
let person = reactive({
name: '张三',
age: 18,
car:{
c1: 'asd',
c2: 'das'
}
})
function changeName (){
person.name = '李四'
}
function changeAge (){
person.age = 19
}
function changeC1(){
person.car.c1 = 'qqq'
}
function changeC2(){
person.car.c2 = 'www'
}
function changeCar(){
person.car = {c1:'yyy', c2:'jjj'}
}
// 监视响应式对象中的某个属性,且该变量是基本类型的,要写成函数式
/* watch(()=>person.name, (newVal, oldVal)=>{
console.log('person.name变化了', newVal, oldVal)
}) */
// 监视响应式对象中的某个属性,且该属性是对象类型的,可以直接写,也能写函数,推荐写函数
watch(()=>person.car, (newVal, oldVal)=>{
console.log('person.car变化了', newVal, oldVal)
}, {deep:true})
</script>
<style scoped>
button {
margin: 10px;
}
</style>
情况5:监视多个数据
<template>
<div>
<h5>情况4:监视【ref】定义的值是【对象类型】的数据</h5>
姓名: {{ person.name }} <br>
年龄: {{ person.age }} <br>
车辆:{{ person.car.c1 }}, {{ person.car.c2 }} <br>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="changeC1">修改c1</button>
<button @click="changeC2">修改c2</button>
<button @click="changeCar">修改car</button>
</div>
</template>
<script lang="ts">
export default {
name: 'Person'
}
</script>
<script setup lang="ts">
import {reactive, watch} from 'vue'
let person = reactive({
name: '张三',
age: 18,
car:{
c1: 'asd',
c2: 'das'
}
})
function changeName (){
person.name = '李四'
}
function changeAge (){
person.age = 19
}
function changeC1(){
person.car.c1 = 'qqq'
}
function changeC2(){
person.car.c2 = 'www'
}
function changeCar(){
person.car = {c1:'yyy', c2:'jjj'}
}
// 此时newVal和oldVal是数组与前面的对应
watch([()=>person.name, ()=>person.age], (newVal, oldVal)=>{
console.log('变化了', newVal, oldVal)
})
</script>
<style scoped>
button {
margin: 10px;
}
</style>
6. watchEffect
相较于watch,watchEffect不需要指定监视对象,而是响应式的追踪对象;
<template>
<div>
<h5>情况4:监视【ref】定义的值是【对象类型】的数据</h5>
姓名: {{ person.name }} <br>
年龄: {{ person.age }} <br>
车辆:{{ person.car.c1 }}, {{ person.car.c2 }} <br>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="changeC1">修改c1</button>
<button @click="changeC2">修改c2</button>
<button @click="changeCar">修改car</button>
</div>
</template>
<script lang="ts">
export default {
name: 'Person'
}
</script>
<script setup lang="ts">
import {reactive, watch, watchEffect} from 'vue'
let person = reactive({
name: '张三',
age: 18,
car:{
c1: 'asd',
c2: 'das'
}
})
function changeName (){
person.name = '李四'
}
function changeAge (){
person.age += 1
}
function changeC1(){
person.car.c1 = 'qqq'
}
function changeC2(){
person.car.c2 = 'www'
}
function changeCar(){
person.car = {c1:'yyy', c2:'jjj'}
}
// 此时newVal和oldVal是数组与前面的对应
/* watch([()=>person.name, ()=>person.age], (newVal, oldVal)=>{
let [newname, newage] = newVal
if( newage > 23 ){
console.log('发送请求')
}
}) */
// 如果采用watchEffect,全自动的watch;
watchEffect(()=>{
if( person.age > 23 ){
console.log('发送请求')
}
})
</script>
<style scoped>
button {
margin: 10px;
}
</style>
7. 标签的ref
属性
利用ref
于document.getElementById('')
的区别在于,前者是局部的,不会受到整体的干扰;
<template>
<h2> 北京 </h2>
<h2 ref="title"> 师范 </h2>
<h2> 大学 </h2>
<button @click="output"> 点击一下输出样式 </button>
</template>
<script lang="ts">
export default {
name: 'Person'
}
</script>
<script setup lang="ts">
import {ref, defineExpose} from 'vue'
// 这里变量名和template中的ref中的变量名对应上了
let title = ref()
function output(){
console.log(title.value)
}
// 在这里可以使调用该模块的模块得到该模块的内容
defineExpose({title})
</script>
<style scoped>
/* 这里的scoped是局部样式,防止和子文件样式出现重复而全部修改
无脑加上就好 */
</style>
day 3
8. 回顾TS中的接口_泛型_自定义类型
一般在文件目录src
下面有一个types
文件夹,types
中有一个index.ts
文件可以直接导入(导入过程不需要写文件)
这里文件表示泛型和接口的意思,一般是结合后端使用,避免缺失参数;
// 定义一个接口,用于限制person对象的具体属性 string,number小写
// 使用export暴露
export interface PersonInter {
id: string,
name: string,
// ?问号表示可有可无
age?: number
}
// 一个自定义类型
export type Persons = Array<PersonInter>
// 也可以这样写 export type Persons = PersonInter[]
<template>
<div class="app">
</div>
</template>
<script lang="ts">
export default {
name: 'Person'
}
</script>
<script setup lang="ts">
// PersonInter是类型,必须使用type前置
import {type PersonInter, type Persons} from '@/types'
import {reactive} from 'vue'
// 单个
let person:PersonInter = {id: 'asdasdas01', name: '张三', age: 18}
// 数组 第一种形式 尖括号表示的是泛型:即符合规范
let persons:Array<PersonInter> = [
{id: 'asdasdas01', name: '张三', age: 18},
{id: 'asdasdas02', name: '李四', age: 20},
{id: 'asdasdas03', name: '王五', age: 33}
]
// 数组 第二种形式 这里结合index.ts文件
let personList:Persons = [
{id: 'asdasdas01', name: '张三', age: 18},
{id: 'asdasdas02', name: '李四', age: 20},
{id: 'asdasdas03', name: '王五', age: 33}
]
let personsReactive = reactive<Persons>([
{id: 'asdasdas01', name: '张三', age: 18},
{id: 'asdasdas02', name: '李四', age: 20},
{id: 'asdasdas03', name: '王五', age: 33}
])
</script>
<style scoped>
.app {
background-color: #ddd;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
</style>
9. props
的使用
将父组件中的数据传给子组件
vue
中html
标签属性前面加冒号表示表达式
<h2 a="1+1" :b="1+1"></h2>
父组件如下:
<template>
<Person a="哈哈" :list="persons"/>
</template>
<script lang="ts">
export default {
name: 'App'
}
</script>
<script lang="ts" setup>
import Person from './components/Person.vue'
import {reactive} from 'vue'
import {type Persons} from '@/types'
let persons = reactive<Persons>([
{id: 'asdasdas01', name: '张三', age: 18},
{id: 'asdasdas02', name: '李四', age: 20},
{id: 'asdasdas03', name: '王五', age: 33}
])
</script>
<style scoped>
</style>
子组件如下:
<template>
<div class="app">
<ul>
<li v-for="a in x.list" :key="a.id">{{ a.name }} - {{ a.age }}</li>
</ul>
</div>
</template>
<script lang="ts">
export default {
name: 'Person'
}
</script>
<script setup lang="ts">
import {type Persons } from '@/types';
// 下面可以不用引入defineProps,withDefaults
import {defineProps, withDefaults} from 'vue'
// 接收a
// defineProps(['a'])
// 接收a和list,同时将props保存起来
// let x = defineProps(['a', 'list'])
// x = {a:'哈哈'}
// 接收list+限制类型
// let x = defineProps<{list:Persons}>()
// 接收list + 限制类型 + 限制必要性 + 指定默认值
// list后面加?表示父组件可传可不传, 第二个list后面需要跟函数
let x = withDefaults(defineProps<{list?:Persons}>(),{
list:()=>[{id:'adasdasdaszxc01', name:'kang', age:20}]
})
</script>
<style scoped>
.app {
background-color: #ddd;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
</style>
10. 生命周期
组件的生命周期有四个阶段:创建,挂载,更新,销毁;
钩子:生命周期函数 (阶段前,阶段完毕) 钩子一共有8个
<template>
<div class="app">
{{ message }}
</div>
</template>
<script lang="ts">
export default {
name: 'Person'
}
</script>
<script setup lang="ts">
import {onBeforeMount, onMounted, onBeforeUpdate,
onUpdated, onBeforeUnmount, onUnmounted} from 'vue'
let message = '哈哈'
onBeforeMount(()=>{
console.log('ok')
})
onUnmounted(()=>{
console.log('一般来说需要搭配v-if')
})
</script>
<style scoped>
.app {
background-color: #ddd;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
</style>
11. 自定义Hooks
Hooks是组合式功能的体现,其创建过程类似于8节中在文件目录src下创建一个hooks的文件夹,里面存放useXxxx.ts文件,实例如下:
useAdd.ts
import { computed } from '@vue/reactivity'
import {ref, onMounted} from 'vue'
export default function(){
let sum = ref(0)
let bigSum = computed(()=>{
return sum.value * 10
})
function add(){
sum.value += 1
}
onMounted(()=>{
sum.value += 1
})
return {sum, add, bigSum}
}
useDog.ts
import {reactive, onMounted} from 'vue'
import axios from 'axios'
export default function(){
let dogList = reactive([
'https://images.dog.ceo/breeds/pembroke/n02113023_4136.jpg'
])
// axios 的 实例用法1
/* function addDog(){
axios.get('https://dog.ceo/api/breed/pembroke/images/random').then(
response => {dogList.push(response.data.message)},
error => {console.log(error)}
)
} */
// axios 的 实例用法2
async function addDog() {
try {
let result = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')
dogList.push(result.data.message)
} catch (error) {
console.log(error)
}
}
onMounted(()=>{
addDog()
})
return {dogList, addDog}
}
这样组件中可以直接应用hooks文件下的内容:
<template>
<div class="app">
<h2> sum: {{ sum }}; bigSum: {{ bigSum }}</h2>
<button @click="add"> 点我sum+1</button>
<hr>
<img v-for="(dog,index) in dogList" :src="dog" :key="index"> <br>
<button @click="addDog"> 点我再添加一只狗 </button>
</div>
</template>
<script lang="ts">
export default {
name: 'Person'
}
</script>
<script setup lang="ts">
import useAdd from '@/hooks/useAdd'
import useDog from '@/hooks/useDog'
const {sum, add, bigSum} = useAdd()
const {dogList, addDog} = useDog()
</script>
<style scoped>
.app {
background-color: #ddd;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
img {
height: 150px;
margin-right: 10px;
}
</style>
一般在文件目录src
下面有一个types
文件夹,types
中有一个index.ts
文件可以直接导入(导入过程不需要写文件)
12. 路由 基本切换效果
路由和路由器不一样
路由类似于键值对,route
路由器类似于管理路由,router
路由的创建与Hooks和TS接口类似,其创建过程类似于8节中在文件目录src下创建一个router的文件夹,里面存放index.ts文件(无需导入文件名),实例如下:
index.ts 实例如下:
// 创建一个路由器,并暴露出去
// 第一步:引入createRouter
import {createRouter, createWebHistory} from 'vue-router'
// 第二步:引入可能要呈现的组件
import Home from '@/components/Home.vue'
import About from '@/components/About.vue'
import News from '@/components/News.vue'
// 第三步:创建路由器
const router = createRouter({
// history表示路由的工作模式
history: createWebHistory(),
routes:[// 一个个路由规则
{
path: '/home',
component: Home
},
{
path: '/news',
component: News
},
{
path: '/about',
component: About
}
]
})
export default router
在这里我们只是创建了路由,于此同时,我们需要把路由加载到项目中,所以接下来我们需要在main.ts
文件中操作;
// 引入createApp 用于创建应用
import { createApp } from 'vue'
// 引入app根组件
import App from './App.vue'
// 引入路由器
import router from './router'
// 创建一个应用
const app = createApp(App)
// 使用路由器
app.use(router)
// 挂载整个应用到app容器中
app.mount('#app')
为了在App.vue
引入组件,我们还需要做一些操作,使用RouterView
, RouterLink
:
<template>
<h2> Vue 路由测试 </h2>
<!-- 导航区 -->
<div class="navigate">
<RouterLink to="/home">主页</RouterLink>
<RouterLink to="/news">新闻</RouterLink>
<RouterLink to="/about">关于</RouterLink>
</div>
<!-- 展示区 -->
<div class="main_content">
<RouterView/>
</div>
</template>
<script lang="ts">
export default {
name: 'App'
}
</script>
<script lang="ts" setup>
import {RouterView, RouterLink} from 'vue-router'
</script>
<style scoped>
h2 {
background: skyblue;
text-align: center;
}
div.navigate {
text-align: center;
}
div.navigate a{
text-align: center;
margin: 50px;
}
div.main_content{
text-align: center;
height: 500px;
}
</style>
就完毕了,这里要用RouterLink替换a标签,并把href改成to,用RouterView表示component的插入位置;
13. 路由 两个注意点
路由组件一般放在pages
或者views
文件夹,一般组件通常放在components
文件夹;
通过点击导航,视觉上效果消失了的组件,默认是被销毁掉的,需要的时候再去挂载;
14. 路由 路由器的工作模式
路由器有两种工作模式,history和hash,前端一般用history,后端一般用hash;
15. 路由 to的两种写法
%% 第一种: %%
<RouterLink to="/home" active-class="active">主页</RouterLink>
%% 第二种: %%:
<RouterLink :to="{path='/home'}" active-class="active">主页</RouterLink>
这里再介绍一下 active-class ,由于RouterLink在渲染的时候会变成a标签,所以样式可以类似于设置如下:
a.active {
text-align: center;
margin: 50px;
color: red;
}
16. 路由 命名路由
可以在路由的index.ts文件中添加添加名字:
// 创建一个路由器,并暴露出去
// 第一步:引入createRouter
import {createRouter, createWebHistory, createWebHashHistory} from 'vue-router'
// 第二步:引入可能要呈现的组件
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
import News from '@/views/News.vue'
// 第三步:创建路由器
const router = createRouter({
// 路由的工作模式:history模式 不带#号 优先
history: createWebHistory(),
// 路由的工作模式:hash模式 带#号
// history: createWebHashHistory(),
routes:[// 一个个路由规则
{
name: 'zhuye',
path: '/home',
component: Home
},
{
name: 'xinwen',
path: '/news',
component: News
},
{
name: 'guanyu',
path: '/about',
component: About
}
]
})
export default router
17. 路由 嵌套路由
子级不用加斜杠,但是to的时候需要完整的使用\news\detail
;
// 创建一个路由器,并暴露出去
// 第一步:引入createRouter
import {createRouter, createWebHistory, createWebHashHistory} from 'vue-router'
// 第二步:引入可能要呈现的组件
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
import News from '@/views/News.vue'
import Detail from '@/views/Detail.vue'
// 第三步:创建路由器
const router = createRouter({
// 路由的工作模式:history模式 不带#号 优先
history: createWebHistory(),
// 路由的工作模式:hash模式 带#号
// history: createWebHashHistory(),
routes:[// 一个个路由规则
{
name: 'zhuye',
path: '/home',
component: Home
},
{
name: 'xinwen',
path: '/news',
component: News,
children:[
{
path: 'detail', //子级不用写斜杠
component: Detail,
}
]
},
{
name: 'guanyu',
path: '/about',
component: About
}
]
})
export default router
18. 路由 query 参数
在传参数给组件之前,我们需要给接收参数的组件添加一个Hooks:useRoute
<template>
<ul class="news-list">
<li>编号:{{ route.query.id }}</li>
<li>标题:{{ route.query.title }}</li>
<li>内容:{{ route.query.content }}</li>
</ul>
</template>
<script setup lang="ts">
import {useRoute} from 'vue-router'
import {toRefs} from 'vue'
let route = useRoute()
// 如果需要解构
let {query} = toRefs(route)
</script>
<style scoped>
</style>
这里在RouterLink
中添加query参数:
<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>
19. 路由 params 参数
首先需要在router中设置占位:就是在path中添加/:
// 创建一个路由器,并暴露出去
// 第一步:引入createRouter
import {createRouter, createWebHistory, createWebHashHistory} from 'vue-router'
// 第二步:引入可能要呈现的组件
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
import News from '@/views/News.vue'
import Detail from '@/views/Detail.vue'
// 第三步:创建路由器
const router = createRouter({
// 路由的工作模式:history模式 不带#号 优先
history: createWebHistory(),
// 路由的工作模式:hash模式 带#号
// history: createWebHashHistory(),
routes:[// 一个个路由规则
{
name: 'zhuye',
path: '/home',
component: Home
},
{
name: 'xinwen',
path: '/news',
component: News,
children:[
{
name: 'xiangqing',
path: 'detail/:id/:title/:content?', // 需要占位 ?代表可传可不传
component: Detail,
}
]
},
{
name: 'guanyu',
path: '/about',
component: About
}
]
})
export default router
params只能使用route
的name
,而不能使用path
,同时传参数只能使用基本类型,而不能使用数组和对象;
<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: 'xiangqing',
params: {// 只能传基本类型,不能是对象和数组
id: news.id,
title: news.title,
content:news.content,
}
}">
{{ news.title }}
</RouterLink>
</li>
</ul>
20. 路由 props配置
props:true 布尔值写法,只能搭配params使用,path需要占位
props(route){return route.query} 函数式写法,其中route可以操作params和query
props:{id:xx,title:xx,content:xx}
其原理就相当于在<Detail/>
中 添加了 <Detail id=xx title=xx content=xx/>
21. 路由 replace属性
类似于浏览器的前进后退机制;
<div class="navigate">
<RouterLink replace to="/home" active-class="active">主页</RouterLink>
<RouterLink replace :to="{name:'xinwen'}" active-class="active">新闻</RouterLink>
<RouterLink replace to="/about" active-class="active">关于</RouterLink>
</div>
直接在RouterLink
后面添加replace
,不会留下记录;
22. 路由 编程式路由导航
可以做到不点标签直接跳转的效果,脱离RouterLink实现跳转;router.push()
router.replace()
括号中to能怎么写,括号就能怎么写;
setTimeout(()=>{
// 在此处执行跳转
}, 3000) // 3秒后执行函数
实例代码如下:
import {onMounted} from 'vue'
import { useRouter } from 'vue-router';
const router = useRouter()
onMounted(()=>{
setTimeout(()=>{
router.push('/news')
}, 3000)
})
按钮点击实例代码如下:
<template>
<div class="news">
<!-- 导航区 -->
<ul>
<li v-for="news in NewsList" :key="news.id">
<button @click="showContent(news)">点击获取详情</button>
<RouterLink :to="{
name: 'xiangqing',
params: {// 只能传基本类型,不能是对象和数组
id: news.id,
title: news.title,
content:news.content,
}
}">
{{ news.title }}
</RouterLink>
</li>
</ul>
<!-- 展示区 -->
<div class="news-content">
<RouterView></RouterView>
</div>
</div>
</template>
<script setup lang="ts">
import type path from 'path';
import {reactive} from 'vue'
import {RouterView, RouterLink, useRouter} from 'vue-router'
interface News{
id: string,
title: string,
content: string,
}
const NewsList = reactive<News[]>([
{id: 'asdasd01', title:'新闻阿斯达所多01', content: '在成长速度还01'},
{id: 'asdasd02', title:'新闻阿斯达所多02', content: '在成长速度还02'},
{id: 'asdasd03', title:'新闻阿斯达所多03', content: '在成长速度还03'},
{id: 'asdasd04', title:'新闻阿斯达所多04', content: '在成长速度还04'},
])
const router = useRouter()
function showContent(news:News){
router.push(
{
name: 'xiangqing',
params: {// 只能传基本类型,不能是对象和数组
id: news.id,
title: news.title,
content:news.content,
}
})
}
</script>
<style scoped>
div.news {
background-color: yellow;
height: 500px;
}
div.news-content {
background-color: aqua;
height: 300px;
width: 200px;
}
</style>
23. 路由 重定向
自动切换url,在router文件中补充规则:
{
path: '/',
redirect: '/home'
}
day 4
24. pinia 选项式写法
pinia环境首先需要安装
npm install pinia
安装之后,pinia的创建与Hooks和TS接口类似,其创建过程类似于8节中在文件目录src下创建一个store的文件夹,里面存放对应components中的文件实例如下:
在main.ts文件中需要引入pinia
import {createApp} from 'vue'
import App from './App.vue'
import {createPinia} from 'pinia'
import router from './router'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.use(router)
app.mount('#app')
Count.vue如下:
<template>
<div class="count">
<h2>当前求和为: {{ sum }}, bigsum:{{ bigSum }}, smallsum:{{ smallSum }}</h2>
<!-- 这里.number表示value数值后面的符号为单位 -->
<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">
export default {
name: 'Count'
}
</script>
<script setup lang="ts">
import {useCountStore} from '@/store/Count'
import {storeToRefs} from 'pinia'
let countStore = useCountStore()
// storeToRefs只会关注store中的数据
let {sum, n, bigSum, smallSum} = storeToRefs(countStore)
// 以下两种方式都能拿到state中的数据
// console.log(countStore.sum)
// console.log(countStore.$state.sum)
// 方法
function add(){
// 第一种修改方式
// countStore.sum += countStore.n
// 第二种修改方式
// countStore.$patch({
// sum: 100
// })
// 第三种修改方式 复杂内容可以放到store中的actions中
countStore.increment()
}
function minus(){
sum.value -= n.value
}
</script>
<style scoped>
.count {
background-color: skyblue;
padding: 10px;
border-radius: 10px;
box-shadow: 0 0 10px;
margin-bottom: 20px;
}
select, button {
margin: 0 10px;
height: 25px;
}
</style>
Count.ts 如下:
import {defineStore} from 'pinia'
import {ref} from 'vue'
export const useCountStore = defineStore('Count', {
// 写函数
actions:{
increment(){
this.sum += this.n
}
},
// 真正存储数据的地方
state(){
return {
sum: ref(0),
n: ref(1),
}
},
// 类似于vue中的computed属性
getters:{
bigSum: state=>state.sum*10,
smallSum():number{
return this.sum - 1000
}
}
})
LoveTalk.vue如下:
<template>
<div class="talk">
<button @click="getMessage"> 点击获取一句土味情话</button>
<ul>
<li v-for="talk in talkList" :key="talk.id">{{ talk.content }}</li>
</ul>
</div>
</template>
<script lang="ts">
export default {
name: 'LoveTalk'
}
</script>
<script setup lang="ts">
import {useLoveStore} from '@/store/LoveTalk'
import {storeToRefs} from 'pinia'
let loveStore = useLoveStore()
let {talkList} = storeToRefs(loveStore)
loveStore.$subscribe((mutate, state)=>{
// mutate是变化的东西,state是后面的状态
console.log('lovestore保存的数据发生了变化!', mutate, state)
// 存储在本地的talkList变量中,由于talkList只能接受字符串数据,所以需要JSON.stringify
localStorage.setItem('talkList', JSON.stringify(state.talkList))
})
async function getMessage(){
loveStore.getTalk()
}
</script>
<style scoped>
.talk {
background-color: orange;
padding: 10px;
border-radius: 10px;
box-shadow: 0 0 10px;
}
</style>
LoveTalk.ts如下:
import {defineStore} from 'pinia'
import {nanoid} from 'nanoid' // 生成id
import axios from 'axios'
export const useLoveStore = defineStore('LoveTalk', {
actions:{
async getTalk(){
let {data:{content}} = await axios.get('https://api.uomg.com/api/rand.qinghua')
let obj = {id: nanoid(), content: content}
this.talkList.unshift(obj)
}
},
// 真正存储数据的地方
state(){
return {
talkList: JSON.parse(localStorage.getItem('talkList') as string ) || []
}
}
})
25. pinia 组合式写法
这里相对于选项式,我们需要把state中的部分转化为响应式才有效,然后添加return返回:
LoveTalk.ts如下:
import {defineStore} from 'pinia'
import {nanoid} from 'nanoid'
import axios from 'axios'
import {reactive} from 'vue'
export const useLoveStore = defineStore('LoveTalk', ()=>{
// 这里写state中的部分
let talkList = reactive(JSON.parse(localStorage.getItem('talkList') as string ) || [])
async function getTalk(){
console.log('获取一句')
try {
let {data:{content}} = await axios.get('https://api.uomg.com/api/rand.qinghua')
let obj = {id: nanoid(), content: content}
talkList.unshift(obj)
} catch (error) {
console.log('获取失败!')
}
}
return {talkList, getTalk}
})
day 5
26. 组件通信_props
Father.vue 如下:
<template>
<div class="father">
<h2>这是父组件</h2>
<h4>汽车:{{car}}</h4>
<h4 v-if="toy">获取到儿子的玩具:{{ toy }}</h4>
<Child :car="car" :sendToy="getToy"></Child>
</div>
</template>
<script lang="ts">
export default {
name: 'Props'
}
</script>
<script setup lang="ts">
import Child from './Child.vue'
import {ref} from 'vue'
// 数据
let car = ref('benz')
let toy = ref('')
// 方法
function getToy(val:string){
toy.value = val
}
</script>
<style scoped>
div.father {
margin: 30px;
background-color: yellow;
width: 500px;
height: 300px;
border-radius: 10px;
box-shadow: 0 0 10px;
}
</style>
Child.vue如下:
<template>
<div class="child">
<h2>这是子组件</h2>
<h4>玩具:{{ toy }}</h4>
<h4>父亲给的车:{{ car }}</h4>
<button @click="sendToy(toy)">点击把玩具交给父亲</button>
</div>
</template>
<script lang="ts">
export default {
name: 'Child'
}
</script>
<script setup lang="ts">
import { ref } from 'vue'
// 数据
let toy = ref('atm')
defineProps(['car', 'sendToy'])
</script>
<style scoped>
div.child {
background-color: gray;
width: 80%;
height: 70%;
margin-left: 20px;
border-radius: 10px;
box-shadow: 0 0 10px;
}
</style>
27. 组件通信_custom-event
标签中使用@开头的props表示的是事件,比如@click是点击事件,如果我们需要打印事件,我们可以使用$event当做参数放入函数中获取事件;
事件一般用串串的形式,而回调函数使用小驼峰
Father.vue 代码如下:
<template>
<div class="father">
<h2>这是父组件</h2>
<h3 v-if="toy">儿子给的玩具{{ toy }}</h3>
<!-- 这里send-toy表示时间的名字,getToy表示事件触发时使用的函数回调 -->
<!-- 事件一般用串串的形式,而回调函数使用小驼峰 -->
<Child @send-toy="getToy"></Child>
<!-- 这里可以使用$event 提取出事件进行打印 -->
<!-- <Child @send-toy="getToy(param1, param2, $event)"></Child> -->
</div>
</template>
<script lang="ts">
export default {
name: 'Event'
}
</script>
<script setup lang="ts">
import Child from './Child.vue'
import {ref} from 'vue'
let toy = ref('')
function getToy(val:string){
toy.value = val
}
</script>
<style scoped>
div.father {
margin: 30px;
background-color: yellow;
width: 500px;
height: 300px;
border-radius: 10px;
box-shadow: 0 0 10px;
}
</style>
Child.vue 代码如下:
<template>
<div class="child">
<h2>这是子组件</h2>
<h3>儿子的玩具{{ toy }}</h3>
<button @click="emit('send-toy', toy)"> 发送给父亲</button>
</div>
</template>
<script lang="ts">
export default {
name: 'Child'
}
</script>
<script setup lang="ts">
import {ref} from 'vue'
let toy = ref('atm')
let emit = defineEmits(['send-toy'])
</script>
<style scoped>
div.child {
background-color: gray;
width: 80%;
height: 70%;
margin-left: 20px;
border-radius: 10px;
box-shadow: 0 0 10px;
}
</style>
28. 组件通信_mitt
类似于总线bus,收数据的:在公共区域提前绑定好事件;提供数据的:在合适的时候触发事件;
首先需要安装mitt
npm i mitt
由于mitt属于一个工具,所以在src目录下创建一个utils文件夹,创建一个emitter.ts
文件;
emitter.ts 文件内容如下:
// 引入 mitt
import mitt from 'mitt'
// 调用mitt得到emitter,emitter可以绑定事件和触发事件
const emitter = mitt()
// 绑定事件
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()
}, 3000)
// 暴露emitter
export default emitter
同时,需要在mian.ts文件中进行引入:
import {createApp} from 'vue'
import App from './App.vue'
import {createPinia} from 'pinia'
import router from './router'
import emitter from './utils/emitter'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.use(router)
app.mount('#app')
注意,这里不需要app.use;
Child1.vue代码如下:
<template>
<div class="child">
<h2>这是子1</h2>
<h3> 玩具 {{toy}}</h3>
<button @click="emitter.emit('send-toy', toy)">点击发送玩具</button>
</div>
</template>
<script lang="ts">
export default {
name: 'Child1'
}
</script>
<script setup lang="ts">
import emitter from '@/utils/emitter';
import {ref} from 'vue'
let toy = ref('atm')
</script>
<style scoped>
div.child {
background-color: gray;
width: 80%;
height: 70%;
margin-left: 20px;
border-radius: 10px;
box-shadow: 0 0 10px;
}
</style>
Child2.vue代码如下:
<template>
<div class="child">
<h2>这是子2</h2>
<h3> 电脑 {{computer}}</h3>
<h3 v-if="toy"> 玩具 {{toy}}</h3>
</div>
</template>
<script lang="ts">
export default {
name: 'Child2'
}
</script>
<script setup lang="ts">
import {ref, onUnmounted} from 'vue'
import emitter from '@/utils/emitter';
let computer = ref('wxr')
let toy = ref('')
emitter.on('send-toy', (val:any)=>{
toy.value = val
})
// 防止被卸载后占用内存
onUnmounted(()=>{
emitter.off('send-toy')
})
</script>
<style scoped>
div.child {
background-color: gray;
width: 80%;
height: 70%;
margin-left: 20px;
border-radius: 10px;
box-shadow: 0 0 10px;
}
</style>
29. 组件通信_v-model
UI组件库底层大量使用v-model进行通信;
e
v
e
n
t
:对于原生事件,
event: 对于原生事件,
event:对于原生事件,event就是事件对象; 能==> .target
对于自定义事件,$event就是触发事件时,所传递的数据; 不能==> .target
Father.vue如下:
<template>
<div>
<h2>父组件</h2>
<h4>value : {{ username }}</h4>
<!-- v-model用在html标签上 下面两种方法等价-->
<!-- <input type="text" v-model="username"> -->
<!-- <input type="text" :value="username" @input="username=(<HTMLInputElement>$event.target).value"> -->
<!-- v-model用在组件标签上 下面两种方法等价-->
<!-- <MyCustomInput v-model="username"></MyCustomInput> -->
<!-- <MyCustomInput :modelValue="username" @update:modelValue="username = $event"></MyCustomInput> -->
<!-- 自定义modelValue 需要把UI组件中所有的modelValue改成qwe -->
<MyCustomInput v-model:qwe="username"></MyCustomInput>
</div>
</template>
<script lang="ts">
export default {
name: 'Model'
}
</script>
<script setup lang="ts">
import {ref} from 'vue'
import MyCustomInput from './MyCustomInput.vue';
let username = ref('zhangsan')
</script>
<style scoped>
</style>
自定义组件MyCustomInput.vue如下:
<!-- 要是用v-model,必须要modelValue和update:modelValue -->
<template>
<div>
<input
type="text"
:value="modelValue"
@input="emit('update:modelValue', (<HTMLInputElement>$event.target).value)"
>
</div>
</template>
<script lang="ts">
export default {
name: 'MyCustomInput'
}
</script>
<script setup lang="ts">
defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>
<style scoped>
input {
border: 2px solid black;
background-image: linear-gradient(45deg, red, yellow, green);
height: 30px;
font-size: 20px;
color: white;
}
</style>
30. 组件通信_$attrs
使用props,未用defineProps的数据,子组件会使用attrs接收;
Father.vue
<template>
<div class="father">
<h2> 父组件 </h2>
<h4> {{ data }} </h4>
<!-- v-bind="{x: 100, y:200}" 相当于 :x="100" :y="200" -->
<Child v-bind="data"></Child>
</div>
</template>
<script lang="ts">
export default {
name: 'AttrsListeners'
}
</script>
<script setup lang="ts">
import Child from './Child.vue'
import {reactive} from 'vue'
let data = reactive({
a: 100,
b: 200,
c: 300
})
</script>
<style scoped>
div.father {
margin: 30px;
background-color: yellow;
width: 500px;
height: 300px;
border-radius: 10px;
box-shadow: 0 0 10px;
}
</style>
Child.vue
<template>
<div class="child">
<h2> 子组件 </h2>
<Grandson v-bind="$attrs"> </Grandson>
</div>
</template>
<script lang="ts">
export default {
name: 'Child'
}
</script>
<script setup lang="ts">
import Grandson from './Grandson.vue'
</script>
<style scoped>
div.child {
margin: 30px;
background-color: red;
width: 500px;
height: 300px;
border-radius: 10px;
box-shadow: 0 0 10px;
}
</style>
Grandson.vue
<template>
<div class="grandson">
<h2> 孙组件 </h2>
<h4> {{ $attrs }}</h4>
</div>
</template>
<script lang="ts">
export default {
name: 'Grandson'
}
</script>
<script setup lang="ts">
</script>
<style scoped>
div.grandson {
margin: 30px;
background-color: green;
width: 500px;
height: 300px;
border-radius: 10px;
box-shadow: 0 0 10px;
}
</style>
31. 组件通信_$refs
与$parent
$refs
可以获取html下所有的ref
组件,$parent
可以获取父组件中的数据;都要结合defineExpose()
Father.vue 代码如下:
<template>
<div>
<h2> 父组件 </h2>
<h4>房产:{{house}}</h4>
<button @click="changeToys">改变c1的toys</button>
<button @click="changeComputer">改变c2的computer</button>
<button @click="getAllChild($refs)">获取所有的子组件实例对象</button>
<Child1 ref="c1"/>
<Child2 ref="c2"/>
</div>
</template>
<script lang="ts">
export default {
name: 'RefChildrenParent'
}
</script>
<script setup lang="ts">
import Child1 from './Child1.vue'
import Child2 from './Child2.vue'
import {ref} from 'vue'
let house = ref(3)
let c1 = ref()
let c2 = ref()
function changeToys(){
c1.value.toys = '修改后的玩具'
}
function changeComputer(){
c2.value.computer = '修改后的电脑'
}
// refs:{[key:string]:any} 或者 refs:any
function getAllChild(refs:{[key:string]:any}){
for(let index in refs){
refs[index].books += 3
}
}
defineExpose({house})
</script>
<style scoped>
</style>
Child1.vue 代码如下:
<template>
<div>
<h2> 子组件1 </h2>
<h4>玩具:{{ toys }}</h4>
<h4>书籍:{{ books }}</h4>
<button @click="minusHouse($parent)">干掉父亲的一套房产</button>
</div>
</template>
<script lang="ts">
export default {
name: 'Child1'
}
</script>
<script setup lang="ts">
import {ref} from 'vue'
let toys = ref('asdsdsaxzczxc')
let books = ref(3)
function minusHouse(parent:any){
parent.house -= 1
}
defineExpose({toys, books})
</script>
<style scoped>
</style>
Child2.vue 代码如下:
<template>
<div>
<h2> 子组件1 </h2>
<h4>电脑:{{ computer }}</h4>
<h4>书籍:{{ books }}</h4>
</div>
</template>
<script lang="ts">
export default {
name: 'Child2'
}
</script>
<script setup lang="ts">
import {ref} from 'vue'
let computer = ref('asdas')
let books = ref(3)
defineExpose({computer, books})
</script>
<style scoped>
</style>
32. 组件通信_provide-inject
常用于祖孙之间越过父亲直接通信;
祖使用provide传送,孙使用inject接收,inject第二个参数是默认值
Father.vue
<template>
<div class="father">
<h2> 父组件 </h2>
<h4>钱:{{ money }}</h4>
<h4 v-for="car in cars">车:{{ car.brand }} 价值 :{{ car.price }}</h4>
<Child></Child>
</div>
</template>
<script lang="ts">
export default {
name: 'ProvidedInject'
}
</script>
<script setup lang="ts">
import Child from './Child.vue'
import {ref, reactive, provide} from 'vue'
let money = ref(100)
let cars = reactive([{
brand: 'benz',
price: 1000,
}])
// 向后代提取数据
provide('qian', money)
provide('che', cars)
</script>
<style scoped>
div.father {
margin: 30px;
background-color: yellow;
width: 500px;
height: 300px;
border-radius: 10px;
box-shadow: 0 0 10px;
}
</style>
Child.vue
<template>
<div class="grandson">
<h2> 孙组件 </h2>
<h4> 钱:{{ money }}</h4>
<h4 v-for="car in cars">车:{{ car.brand }} 价值 :{{ car.price }}</h4>
</div>
</template>
<script lang="ts">
export default {
name: 'Grandson'
}
</script>
<script setup lang="ts">
import {inject} from 'vue'
// 这里inject第二个参数是默认值
let money = inject('qian', '我是默认值')
let cars = inject('che', {})
</script>
<style scoped>
div.grandson {
margin: 30px;
background-color: green;
width: 500px;
height: 300px;
border-radius: 10px;
box-shadow: 0 0 10px;
}
</style>
32. 组件通信_slot默认插槽
slot有三种:默认插槽,具名插槽,作用域插槽;
默认插槽就是在组件中间使用html标签,然后在组件中加入<slot></slot>
;
Father.vue 如下:
<template>
<div class="father">
<Category>
<h2> 热门游戏列表</h2>
<ul>
<li v-for="game in games" :key="game.id">{{ game.name }}</li>
</ul>
</Category>
<Category>
<template v-slot:default>
<h2> 热门美食列表</h2>
<img :src="imgUrl" alt="">
</template>
</Category>
<Category>
<h2> 热门美食列表</h2>
<video :src="videoUrl" controls> </video>
</Category>
</div>
</template>
<script lang="ts">
export default {
name: 'Slot'
}
</script>
<script setup lang="ts">
import Category from './Category.vue';
import {ref, reactive} from 'vue'
let games = reactive([
{id: 'zxzczxczxc01', name: '爱谁谁大所多'},
{id: 'zxzczxczxc02', name: '爱阿萨德按所多'},
{id: 'zxzczxczxc03', name: '爱自行车在线所多'},
{id: 'zxzczxczxc04', name: '爱谁周传雄所多'},
])
let imgUrl = ref('https://z1.ax1x.com/2023/11/19/piNxLo4.jpg')
let videoUrl = ref('http://vjs.zencdn.net/v/oceans.mp4')
</script>
<style scoped>
.father {
display: flex;
background-color: rosybrown;
padding: 20px;
border-radius: 10px;
justify-content: space-evenly;
width: 100%;
}
img, video {
width: 100%;
}
</style>
Child.vue 如下:
<template>
<div class="category">
<slot></slot>
</div>
</template>
<script lang="ts">
export default {
name: 'Category'
}
</script>
<script setup lang="ts">
</script>
<style scoped>
.category {
background-color: skyblue;
border-radius: 10px;
box-shadow: 0 0 10px;
padding: 10px;
width: 200px;
height: 300px;
}
</style>
33. 组件通信_slot具名插槽
具名插槽意思就是具有名字的插槽,这里默认插槽的名字为default,多个slot可以倒序;
Category.vue
<template>
<div class="category">
<slot name="s1"></slot>
<slot name="s2"></slot>
</div>
</template>
<script lang="ts">
export default {
name: 'Category'
}
</script>
<script setup lang="ts">
</script>
<style scoped>
.category {
background-color: skyblue;
border-radius: 10px;
box-shadow: 0 0 10px;
padding: 10px;
width: 200px;
height: 300px;
}
</style>
Father.vue
<template>
<div class="father">
<Category>
<template #s1>
<h2> 热门游戏列表 </h2>
</template>
<template v-slot:s2>
<ul>
<li v-for="game in games" :key="game.id">{{ game.name }}</li>
</ul>
</template>
</Category>
<Category>
<template #s2>
<img :src="imgUrl" alt="">
</template>
<template #s1>
<h2> 热门美食列表 </h2>
</template>
</Category>
<Category>
<template #s2>
<video :src="videoUrl" controls> </video>
</template>
<template #s1>
<h2> 热门美食列表 </h2>
</template>
</Category>
</div>
</template>
<script lang="ts">
export default {
name: 'Slot'
}
</script>
<script setup lang="ts">
import Category from './Category.vue';
import {ref, reactive} from 'vue'
let games = reactive([
{id: 'zxzczxczxc01', name: '爱谁谁大所多'},
{id: 'zxzczxczxc02', name: '爱阿萨德按所多'},
{id: 'zxzczxczxc03', name: '爱自行车在线所多'},
{id: 'zxzczxczxc04', name: '爱谁周传雄所多'},
])
let imgUrl = ref('https://z1.ax1x.com/2023/11/19/piNxLo4.jpg')
let videoUrl = ref('http://vjs.zencdn.net/v/oceans.mp4')
</script>
<style scoped>
.father {
display: flex;
background-color: rosybrown;
padding: 20px;
border-radius: 10px;
justify-content: space-evenly;
width: 100%;
}
img, video {
width: 100%;
}
</style>
34. 组件通信_slot作用域插槽
主要用在子里面有数据,但是父组件想以不同形式进行表示;
Game.vue
<template>
<div class="game">
<h2>游戏列表</h2>
<slot :youxi="games"></slot>
</div>
</template>
<script lang="ts">
export default {
name: 'Category'
}
</script>
<script setup lang="ts">
import {reactive} from 'vue'
let games = reactive([
{id: 'zxzczxczxc01', name: '爱谁谁大所多'},
{id: 'zxzczxczxc02', name: '爱阿萨德按所多'},
{id: 'zxzczxczxc03', name: '爱自行车在线所多'},
{id: 'zxzczxczxc04', name: '爱谁周传雄所多'},
])
</script>
<style scoped>
.game {
background-color: skyblue;
border-radius: 10px;
box-shadow: 0 0 10px;
padding: 10px;
width: 200px;
height: 300px;
}
</style>
Father.vue
<template>
<div class="father">
<Game>
<!-- 子组件中slot传的props中所有的数据都在a的手里-->
<template v-slot="{youxi}">
<ul>
<li v-for="y in youxi" :key="y.id"> {{ y.name }}</li>
</ul>
</template>
</Game>
<Game>
<!-- 子组件中slot传的props中所有的数据都在a的手里-->
<template v-slot="{youxi}">
<ol>
<li v-for="y in youxi" :key="y.id"> {{ y.name }}</li>
</ol>
</template>
</Game>
<Game>
<!-- 子组件中slot传的props中所有的数据都在a的手里-->
<template v-slot="{youxi}">
<h3 v-for="y in youxi" :key="y.id"> {{ y.name }}</h3>
</template>
</Game>
</div>
</template>
<script lang="ts">
export default {
name: 'Slot'
}
</script>
<script setup lang="ts">
import Game from './Game.vue';
</script>
<style scoped>
.father {
display: flex;
background-color: rosybrown;
padding: 20px;
border-radius: 10px;
justify-content: space-evenly;
width: 100%;
}
img, video {
width: 100%;
}
</style>
35. shallowRef 和 shallowReactive
36. readonly 和 shallowReadonly
37. toRaw 和 markRaw
38. customRef
customRef()
预期接收一个工厂函数作为参数,这个工厂函数接受 track
和 trigger
两个函数作为参数,并返回一个带有 get
和 set
方法的对象。
一般来说,track()
应该在 get()
方法中调用,而 trigger()
应该在 set()
中调用。然而事实上,你对何时调用、是否应该调用他们有完全的控制权。
示例 创建一个防抖 ref,即只在最近一次 set 调用后的一段固定间隔后再调用:
import { customRef } from 'vue'
export function useDebouncedRef(value, delay = 200) {
let timeout
return customRef((track, trigger) => {
return {
// 数据被读取时,调用get
get() {
track() // 告诉vue数据value很重要,一旦value变化就去更新
return value
},
// 数据被修改时,调用set
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger() // 通知一下vue数据value变化了
}, delay)
}
}
})
}
39. Teleport
这个组件中有一个 <button>
按钮来触发打开模态框,和一个 class 名为 .modal
的 <div>
,它包含了模态框的内容和一个用来关闭的按钮。
当在初始 HTML 结构中使用这个组件时,会有一些潜在的问题:
-
position: fixed
能够相对于浏览器窗口放置有一个条件,那就是不能有任何祖先元素设置了transform
、perspective
或者filter
样式属性。也就是说如果我们想要用 CSStransform
为祖先节点<div class="outer">
设置动画,就会不小心破坏模态框的布局! -
这个模态框的
z-index
受限于它的容器元素。如果有其他元素与<div class="outer">
重叠并有更高的z-index
,则它会覆盖住我们的模态框。
<Teleport>
提供了一个更简单的方式来解决此类问题,让我们不需要再顾虑 DOM 结构的问题。让我们用 <Teleport>
改写一下 <MyModal>
:
<button @click="open = true">Open Modal</button>
<Teleport to="body">
<div v-if="open" class="modal">
<p>Hello from the modal!</p>
<button @click="open = false">Close</button>
</div>
</Teleport>
40. Suspense
在这个组件树中有多个嵌套组件,要渲染出它们,首先得解析一些异步资源。如果没有 <Suspense>
,则它们每个都需要处理自己的加载、报错和完成状态。在最坏的情况下,我们可能会在页面上看到三个旋转的加载态,在不同的时间显示出内容。
有了 <Suspense>
组件后,我们就可以在等待整个多层级组件树中的各个异步依赖获取结果时,在顶层展示出加载中或加载失败的状态。
<Suspense>
可以等待的异步依赖有两种:
- 带有异步
setup()
钩子的组件。这也包含了使用<script setup>
时有顶层await
表达式的组件。 - 异步组件。
<Suspense>
<!-- 具有深层异步依赖的组件 -->
<Dashboard />
<!-- 在 #fallback 插槽中显示 “正在加载中” -->
<template #fallback>
Loading...
</template>
</Suspense>
41. 全局api转化为应用对象
import {createApp} from 'vue'
import App from './App.vue'
import Hello from './Hello.vue'
//创建应用
const app = createApp(App)
// 定义全局组件
app.component('Hello',Hello)
// 定义全局属性
app.config.globalProperties.x = 99
declare module 'vue'{
interface ComponentCustomProperties{
x: number
}
}
// 定义样式 对组件使用v-beauty改变样式
app.directive('beauty',(element,{value})=>{
element.innerText += value
element.style.color = 'green'
element.style.backgroundColor = 'yellow'
})
// app挂载
app.mount('#app')
// app卸载
setTimeout(()=>{
app.unmount()
}, 3000)