相对于Vue2.x Vue3.给我们带来了一下优化
性能提升
源码升级
TypeScript
新的特性
Composition API(组合API)
1.在Vue2中使用Options API(配置项API),使用这种方法对数据、方法等进行配置会存在一定的问题
2.于是在Vue3.0中,提出了一个新的概念是Composition API,即组合API,将所有的数据、方法等放入setup中,即组合API的入口函数
3.配置项API存在的问题:当开发代码数据过长时,不方便查找某个功能对应的数据、方法或其他生命周期钩子所在的位置,可能需要通篇阅览
4.组合API的优势:可以将每个功能需要的数据、方法等内容放在一块,或者使用hook函数,在setup中使用即可,需要修改时,直接找到对应的hook函数修改即可
setup介绍
setup 有两种表现方式
起初 Vue3.0 暴露变量必须 return 出来,vue3.2只需在script标签中添加setup语法糖
直接在script标签中使用setup函数,需要return
<script lang="ts">
import { ref } from "vue";
export default {
name: 'home',
setup() {
const info = ref('data')
const onChange = () => { info.value = 'datas' }
// 需要return
return { info, onChange};
},
};
</script>
直接使用setup语法糖(主要介绍语法糖)
<script setup lang="ts">
import { ref } from "vue";
const info = ref('data')
const onChange = () => { info.value = 'datas' }
</script>
Vue2&Vue3响应式介绍
Vue2的响应式是基于Object.defineProperty实现的
Vue3的响应式是基于ES6的Proxy+Reflect来实现的
Vue2中存在的问题
- 需要对每个属性进行遍历监听,如果嵌套对象,需要深层监听,造成性能问题
- 对象的新增属性、删除属性、界面不会更新
- vue2的解决方法:通过vm. s e t ( o b j , k e y . v a l ) / v m . set(obj,key.val)/vm. set(obj,key.val)/vm.delete(obj,key)新增属性/删除属性
- 直接通过下标修改数组,界面不会自动更新
- vue2的解决方法:vm.$set(arr,index.value),调用数组的splice方法
语法
<template>
<div>
<p>姓名:{{obj.name}}</p>
<p>年龄:{{obj.age}}</p>
<p>性别:{{obj.sex}}</p>
<el-button @click="addSex">添加性别sex</el-button>
<el-button @click="delAge">删除年龄age</el-button>
</div>
</template>
<script>
export default {
data() { return { obj:{ name:'weihua', age:20 } } },
methods: {
addSex(){
// 控制台打印可以看到数据变了,但是页面没有更新
this.obj.sex = '女'
this.$set(this.obj, 'sex', '女')
console.log("添加性别sex",this.obj);
},
delAge(){
// 控制台打印可以看到数据变了,但是页面没有更新
delete this.obj.age
// 只有使用 $delete $set 才可以触发
this.$delete(this.obj, 'age')
console.log("删除年龄age",this.obj);
}
},
}
</script>
控制台打印
1.可发现对象的sex属性被添加了,age属性被删除了,但是数据未被响应,界面没有更新
2.使用
s
e
t
、
set、
set、delete 可以看出数据的变化会影响到界面的更新
vue3中的优化
- vue2是监听对象的属性,vue3是监听对象 不需要一次性遍历data的属性,可以显著提高性能。需要的时候再进行深度监听
- 基于ES6 Proxy,对被代理对象设置拦截,访问或操作被代理对象都要先通过拦截。所以可以监听对象属性的添加和删除
- 可以原生监听数组,不需要通过重写方法来实现对数组的监控
Vue3响应式实现
Vue3中实现响应式数据的方法是使用ref和reactive,所谓响应式就是界面和数据同步,能实现实时更新
- ref和reactive都属于递归监听,也就是数据的每一层都是响应式的,如果数据量比较大,非常消耗性能
- shallowRef和shallowReactive 都属于非递归监听只会监听数据的第一层
ref
- ref标准是定义一些 基本数据类型(可接收基本数据类型和对象)
- 本质上,可以理解为ref对reactive的二次包装,ref定义的数据访问的时候要多一个.value;
语法
<template>
<div>
<!-- 输入框双向绑定值 -->
<input v-model="info" type="text">
{{ info }}
<!-- 点击按钮替换值 -->
<button @click="clickValue">我是按钮替换值'zzzz'</button>
</div>
</template>
<script setup lang="ts">
// 按需导入API
import { ref } from 'vue'
// 定义初始化赋值
let info = ref('name')
// 定义函数修改info值
const clickValue = () => {
info.value = 'zzzz'
console.log(info); // 打印 name
}
console.log(info); // 打印 zzzz
</script>
控制台打印
1.__v_isRef:true 判断是不是ref 可以通过内置的方法isRef()来判断的
2.在script中使用/修改需要通过info.value方式,模板中使用{{ info }}方式
reactive
- reactive的参数必须是一个对象,包括json数据和数组都可以,否则不具有响应式
- reactive内部是通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等
语法
<template>
<div>
<!-- 输入框双向绑定值 -->
<input v-model="info.name" type="text">
{{ info }} {{ info.name }} {{ info.age }}
<!-- 点击按钮替换值 -->
<button @click="clickValue">我是按钮替换值'zzzz'</button>
</div>
</template>
<script setup lang="ts">
// 按需导入API
import { reactive } from 'vue'
// 定义初始化赋值
let info = reactive({ name: 'weihua', age: '21' })
// 定义函数修改info值
const clickValue = () => {
info.name = 'zzzz'
console.log(info); // 打印 weihua
}
console.log(info); // 打印 zzzz
</script>
控制台打印
ref和reactive的对比
其他新特性
shallowReactive 与 shallowRef
shallowReactive()
shallowReactive:只处理对象最外层属性的响应式(浅响应式)
shallowRef()
shallowRef定义的数据,只有第一层是响应式的,即只有在更改.value的时候才能实现响应式
- 什么时候使用?
- shallowReactive:如果有一个对象数据,结构比较深, 但变化时只是外层属性变化
- shallowRef:如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换
语法(shallowReactive)
<template>
<div>
<h1>姓名:{{info.name}}</h1>
<h2>年龄:{{info.age}}</h2>
<h3>备注信息:{{info.son.item.msg}}</h3>
<!-- 按钮操作区域 -->
<button @click="changeName">修改姓名</button>
<button @click="changeAge">修改年龄</button>
<button @click="changeMsg">修改备注信息</button>
</div>
</template>
<script setup lang="ts">
// 按需导入API
import { shallowReactive } from 'vue'
// 定义了一段数据
// 只将第一层数据做了响应式处理
let info = shallowReactive({
name: 'weihua',
age: 18,
son: {
item:{
msg: '我是msg值信息'
}
}
})
const changeName = () => { info.name = info.name += '~'}
const changeAge = () => { info.age++}
// 因为msg在该对象的最深层,数据变了,但是vue监测不到
// 无法触发页面的更新
const changeMsg = () => { info.son.item.msg = '修改msg值' }
</script>
控制台打印
)
<template>
<div>
<h1>年纪:{{ info.age }}</h1>
<button @click="clickSum">点击+</button>
</div>
</template>
<script setup lang="ts">
// 按需导入API
import { shallowRef } from 'vue'
// info将是一个普通的对象,不具有响应式
let info = shallowRef({
name: 'weihua',
age: 18,
})
// 该操作无法修改age数据,且不触发页面的更新 因为vue监测不到了
const clickSum = () => {
info.age++
}
</script>
控制台打印
readonly 与 shallowReadonly
- readonly: 让一个响应式数据变为只读的(深只读)
- shallowReadonly:让一个响应式数据变为只读的(浅只读)
- 应用场景: 不希望数据被修改时
- 如果使用readonly包裹的person对象的所有属性数据都只读不可需改,即name、age、msg都只读无法修改
- 如果使用shallowReadonly包裹的person对象的所有属性数据,其中浅层次的设为只读,即name、age只读无法修改,但是msg可读可修改,因为msg是深层次的,不受管控
语法(readonly)
<template>
<div>
<h2>姓名:{{info.name }}</h2>
<h2>年龄:{{info.age }}</h2>
<h2>备注信息:{{info.son.item.msg }}</h2>
<!-- 按钮操作区域 -->
<button @click="changeName">修改姓名</button>
<button @click="changeAge">修改年龄</button>
<button @click="changeMsg">修改备注信息</button>
<h2>sum:{{sum}}</h2>
<button @click="clickSum">sum++</button>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, readonly } from 'vue'
// 定义响应式
let sum = ref(0)
// 定义响应式对象
let info = reactive(
{
name:'weihua', age:18,
son:{ item: { msg: '我是msg值信息' } }
}
)
// readonly 将info、sum响应式数据变为只读的(深只读) 点击任何都不能操作,点击报:warn
info = readonly(info)
sum = readonly(sum)
const changeName = () => { info.name = info.name += '~'}
const changeAge = () => { info.age++}
const changeMsg = () => { info.son.item.msg = '修改msg值' }
const clickSum = () => { sum.value++ }
</script>
控制台打印
语法(shallowReadonly)
<template>
<div>
<h2>姓名:{{info.name }}</h2>
<h2>年龄:{{info.age }}</h2>
<h2>备注信息:{{info.son.item.msg }}</h2>
<!-- 按钮操作区域 -->
<button @click="changeName">修改姓名</button>
<button @click="changeAge">修改年龄</button>
<button @click="changeMsg">修改备注信息</button>
<h2>sum:{{sum}}</h2>
<button @click="clickSum">sum++</button>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, shallowReadonly } from 'vue'
// 定义响应式
let sum = ref(0)
// 定义响应式对象
let info = reactive(
{
name:'weihua', age:18,
son:{ item: { msg: '我是msg值信息' } }
}
)
// shallowReadonly 将info、sum响应式数据变为只读的(浅只读) 点击changeName、changeAge、clickSum都不能操作,点击报:warn
info = shallowReadonly(info)
sum = shallowReadonly(sum)
const changeName = () => { info.name = info.name += '~'}
const changeAge = () => { info.age++}
// 因为msg在该对象的最深层无法监控所以可以改变msg值
const changeMsg = () => { info.son.item.msg = '修改msg值' }
const clickSum = () => { sum.value++ }
</script>
控制台打印
toRaw与 markRaw
- toRaw
- 作用:将一个由reactive生成的响应式对象转为普通对象
- 应用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新
- markRaw
- 作用:标记一个对象,使其永远不会再成为响应式对象(即便是再将转成响应式也是无效的)
- 应用场景:有些值不应被设置为响应式的,例如复杂的第三方类库等,当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能
- 语法(toRaw)
<template>
<div>
<h1>姓名:{{ info.name }}</h1>
<h2>年龄:{{ info.age }}</h2>
<h3>备注信息:{{ info.son.item.msg }}</h3>
<!-- 按钮操作区域 -->
<button @click="changeName">修改姓名</button>
<button @click="changeAge">修改年龄</button>
<button @click="changeMsg">修改备注信息</button>
</div>
</template>
<script lang="ts" setup>
import { reactive, toRaw } from 'vue'
// 定义响应式数据
let info = reactive(
{
name: "weihua", age: 18,
son: {
item: { msg: "我是msg值信息"},
},
}
)
// 将响应对象变为普通对象, 点击事件info值发生了改变但是页面将不会去渲染页面,因为转为了普通对象
info = toRaw(info)
const changeName = () => { info.name = info.name += '~'}
const changeAge = () => { info.age++}
const changeMsg = () => {
info.son.item.msg = '修改msg值'
console.log(info)
}
</script>
控制台打印
toRef
- 为源响应式对象上的某个属性创建一个ref对象,二者内部操作的是同一个数据值,更新时二者是同步的
- 区别ref:拷贝了一份新的数据单独操作,更新时相互不影响
- 应用:将某个prop的ref传递给符合函数时,toRef很有用
- toRef之后,两者的数据是互通的,一个数据改变,另一个数据也变,可以理解为同一个地址,但是ref就是不同的地址,数据不是互通的!
- 应用场景:有时候,我们的函数入口是ref类型,需要用到这个来进行转换
- 语法
// 在这个代码中{{}}中读取对象中的属性显得很麻烦,对象.属性名这种方式,所以出现了toRef
<template>
<div>
<h3>state:{{state}}</h3>
<h3>state.age:{{state.age}}</h3>
<h3>age:{{age}}</h3>
<h3>money:{{money}}</h3>
<hr>
<button @click="update">更新数据</button>
</div>
</template>
<script lang="ts" setup>
import { reactive, toRaw, toRef, ref } from 'vue'
// 定义响应式数据
const state = reactive({
age:5,
money:100
})
// 把响应式数据state对象中的某个属性age变成了ref对象了
const age = toRef(state,'age')
console.log(age, 'ageageage');
// ref包装把state.money变成了一个新的ref对象
const money = ref(state.money)
const update = ()=>{
console.log('test');
state.age+=2
age.value+=3
// 不会相互影响
money.value+=10
}
</script>
控制台打印
customRef
- 作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制
- 应用场景:实现防抖效果(修改数据后1s中再触发页面重新解析模板)
- 语法
实现防抖效果:**get:用于读数据进行调用 set:用于修改数据时进行调用**
<template>
<input type="text" v-model="keyWord"/>
<h3>{{keyWord}}</h3>
</template>
<script setup lang="ts">
import {customRef} from 'vue'
// 自定义myRef
function myRef(value: any, delay: number) {
let timer: any = null
return customRef((track,trigger) => {
return {
// 有人读取该属性时调用
get() {
// 追踪set修改value的变化
track()
return value
},
// 有人修改该属性时调用
set(newValue) {
clearTimeout(timer)
timer = setTimeout(() => {
value = newValue
// 通知Vue去重新解析模板(重新调用get)
trigger()
}, delay)
}
}
})
}
let keyWord = myRef("hello",1000)
</script>
控制台打印
计算属性&监听器
computed
- 用法(与Vue2一致),写法有两种
- 支持缓存,只有依赖的数据发生改变才真的重新计算
- 支持 get set存储器方法
- 语法
<template>
<div>name: <input type="text" v-model="info.name" /></div>
<div>age: <input type="text" v-model="info.age" /></div>
<div>改变后的值 {{ computedAge }}</div>
<div>改变后的值 {{ computedName }}</div>
</template>
<script setup lang="ts">
import { computed, ref, reactive } from 'vue'
const info = reactive({
name: 'weihua',
age: '21'
})
// 简单用法 回调函数写法
let computedAge = computed(() => {
return 'name:' + info.name + '-----' + 'age:'+ info.age
})
// 完整用法 参数为options对象(get和set)使用get和set创建可写的ref对象
// 当name的值发生改变时,走get函数函数,那么影响展示数据的变化
// 当展示数据的值在事件中直接改变,那么会走set方法,
// set函数的形参就是你赋给他的值,set方法可以去修改name的值
let computedName = computed({
// 获取computedName 触发的函数
get() {
return info.name + '我在get中增加信息'
},
// 设置computedName 所触发的函数 value 修改的值
set(value: string) {
return info.name = value
}
})
</script>
控制台打印
注:Vue3中移除了filter过滤器,可以使用computed代替过滤器,也可自己封装方法使用
const textSigh = computed(() => {
// value是计算属性执行后,再次执行return里面的函数时传的参数
return (value:any) => {
state.message = value + '解决vue3不支持过滤器filter的问题 '
return state.message
}
})
watch
-
既要指明监视的属性,也要指明监视的回调
-
数据监听,就是监听的某个数据发生了变化后,那么执行相对应的函数,数据不缓存
-
同步异步场景都可以使用,性能开销很大的场景也适合
-
场景:一个数据影响多个数据,主要做业务处理
-
不是必须return
-
watch() 默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数
-
第一个参数:侦听源,侦听源可以是一下几种
- 一个函数,返回一个值
- 一个 ref
- 一个响应式对象(reactive)
- 或是由以上类型的值组成的数组
-
第二个参数:侦听源发生变化时要触发的回调函数
- (newValue, oldValue) => { /* code */}
- 当侦听多个来源时,回调函数接受两个数组,分别对应源数组中的新值和旧值
- ( [ newValue1, newValue2 ] , [ oldValue1 , oldValue2 ]) => {/* code */}
-
第三个参数:可选对象,可以支持一下这些选项
- immediate:侦听器创建时立即触发回调
- deep:如果源是一个对象,会强制深度遍历,以便在深层级发生变化时触发回调函数
- flush:调整回调函数的刷新时机
- onTrack / onTrigger:调试侦听器的依赖
-
语法
监听ref类型
<template>
<input type="text" v-model="refDc">
<div>监听ref:{{refDc}}</div>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
const refDc = ref('swh')
// 监视ref定义的响应式数据
// immediate:true 配置项,代表首次执行监听一次
watch(refDc ,(newValue,oldValue)=>{
console.log('refDc-newValue:',newValue)
console.log('refDc-oldValue:',oldValue)
},{immediate:true})
</scri
控制台打印
监听ref类型(监听多个ref)
<template>
name: <input type="text" v-model="refDc">
age:<input type="text" v-model="refDcAge">
<div>监听多个ref:{{refDc}}</div>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
const refDc = ref('swh')
const refDcAge = ref('21')
// 监视多个ref定义的响应式数据,用数组
// newValue是监听的两个值的新值:['refDc','refDcAge']
// oldValue是监听的两个值的旧值数组:['refDc','refDcAge']
watch([refDc, refDcAge ] ,(newValue,oldValue)=>{
console.log('newValue:',newValue)
console.log('oldValue:',oldValue)
},{immediate:true})
</script>
控制台打印
监听reactive
<template>
name: <input type="text" v-model="infoData.name">
age: <input type="text" v-model="infoData.age">
<div>reactive数据:{{infoData}}</div>
</template>
<script lang="ts" setup>
import { reactive, watch } from "vue";
const infoData = reactive({
name: 'swh',
age: '21'
})
// 监视reactive定义的响应式数据
// 若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue
// 若watch监视的是reactive定义的响应式数据,
// 则强制开启了深度监视 (deep) 此处的deep(false)配置则不生效
// immediate:true 配置项,代表首次执行监听一次
watch(infoData ,(newValue,oldValue)=>{
console.log('监听infoData--newValue',newValue)
console.log('监听infoData--oldValue',oldValue)
},{immediate:true,deep:false})
// 监视reactive定义的响应式数据中的某个属性
// 子组件监听props里的变量,无论是啥类型,都必须是watch(()=>props,function),
// 而不是watch(props,function)
watch(()=>infoData.age,(newValue,oldValue)=>{
console.log('监听infoData.age-newValue',newValue)
console.log('监听infoData.age-oldValue',oldValue)
},{immediate:true,deep:true})
// 监视reactive定义的响应式数据中的某些属性
// newValue、oldValue返回的都是数组,第一位值infoData.name,
// 第二位是infoData.age
watch([()=>infoData.name,()=>infoData.age],(newValue,oldValue)=>{
console.log('监听infoData.age name-newValue',newValue)
console.log('监听infoData.age name-oldValue',oldValue)
},{immediate:true,deep:true})
</script>
控制台打印
监听props 中基本数据类型
语法
<!-- 父组件 -->
<template>
<defineProps :masg="masg" />
<div>
点击父子间按钮修改传给子组件的信息:
<button @click="clickMasg"> 按钮</button>
</div>
</template>
<script setup >
import defineProps from "./components/defineProps.vue"
import { ref } from "vue";
const masg = ref('我是信息父组件')
const clickMasg = () => {
masg.value = '我是信息父组件2223233'
}
</script>
<!-- 子组件 -->
<template>
<button> {{ '父传子信息:' + masg }} </button>
</template>
<script setup>
import { watch } from "vue";
const props = defineProps({
masg: String,
})
// 监听props入参信息
watch(
// 监听其中的属性 侦听一个 getter函数
() => props.masg,
// 如果直接写成props.masg 则不生效
// props.masg,
(newVal, oldVal) => {
console.log('监听基本类型数据masg')
console.log('new', newVal) console.log('old', oldVal)
}
)
原因:
// 无法触发子组件内的监听,因为我们直接修改了masg的value,
// 相当于把masg.value指向了另一个proxy对象。这里是value的指向发生了变化。
// 而子组件内监听的是masg内属性的变化,而不是masg.value内存储的引用指向的变化
具体可以查看下watch源码
watch(
// 直接监听props
props,
(newVal, oldVal) => {
console.log('监听基本类型数据masg')
console.log('new', newVal) console.log('old', oldVal)
}
)
</script>
控制台打印
watchEffect
- watchEffect的是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性
- 跟计算属性有点像:而
watchEffect
函数没返回值注重过程,计算属性注重结果,有返回值 watchEffect
相当于将watch
的依赖源和回调函数合并,当任何你有用到的响应式依赖更新时,该回调函数便会重新执行。不同于watch
,watchEffect
的回调函数会被立即执行(即{ immediate: true }
)- 语法
<template>
name: <input type="text" v-model="info.name">
age:<input type="text" v-model="info.age">
<div>reactive数据:{{info}}</div>
</template>
<script lang="ts" setup>
import { reactive, watchEffect } from "vue";
const info = reactive({
name: 'dc',
age: '21'
})
watchEffect(() => {
// 用到的数据只要发生变化(即 info.age发生变化),则直接重新执行回调。
// const nawValue = info.age
console.log(info.age, '我执行了!')
})
</script>
控制台打印
侦听 props 上的属性变化
<script lang="ts" setup>
// 如果我们想侦听 props 上的属性变化,需要采用第一种写法
// 假设 props 上有个 name 属性
// 下面的写法会生效
watch(
() => props.name,
(count, prevCount) => {
/* ... */
}
)
// 下面的写法不会被触发
watch(props.name, (count, prevCount) => {
/* ... */
})
</script>
生命周期
Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下
生命周期钩子函数需要按需引用
Vue3 | Vue2 | 描述 |
---|---|---|
setup() | beforeCreate | 开始创建组件之前,在beforeCreate和created之前执行。创建的是data和method |
setup() | created | |
onBeforeMount() | beforeMount | 组件挂载到节点上之前执行的函数 |
onMounted() | mounted | 组件挂载完成后执行的函数 |
onBeforeUpdate() | beforeUpdate | 组件更新之前执行的函数 |
onUpdated() | updated | 组件更新完成之后执行的函数 |
onBeforeUnmount() | beforeDestroy | 组件卸载之前执行的函数 |
onUnmounted() | destroyed | 组件卸载完成后执行的函数 |
onDeactivated() | deactivated | 比如从 A 组件,切换到 B 组件,A 组件消失时执行 |
onErrorCaptured() | errorCaptured | 当捕获一个来自子孙组件的异常时激活钩子函数(以后用到再讲,不好展现) |
onRenderTracked() | / | 状态跟踪:它会跟踪页面上所有响应式变量和方法的状态,也就是我们用return返回去的值,他都会跟踪。只要页面有update的情况,他就会跟踪,然后生成一个event对象,我们通过event对象来查找程序的问题所在event 对象属性的详细介绍 key 那边变量发生了变化newValue 更新后变量的值oldValue 更新前变量的值target 目前页面中的响应变量和函 |
onRenderTriggered() | / | 状态触发:当虚拟 DOM 重新渲染被触发时调用。和 https://v3.cn.vuejs.org/api/options-lifecycle-hooks.html#rendertracked类似,接收 debugger event作为参数。此事件告诉你是什么操作触发了重新渲染,以及该操作的目标对象和键event 对象属性的详细介绍 key 那边变量发生了变化newValue 更新后变量的值oldValue 更新前变量的值target 目前页面中的响应变量和函 |
组件间通信
父传子defineProps
- 父组件传值给子组件主要是由父组件为子组件通过v-bind绑定数值,而后传给子组件;子组件则通过defineProps接收使用
- 只能在
<script setup>
中使用 - 不需要被导入即可使用,它会在编译
<script setup>
语法块时一同编译掉 - 必须在
<script setup>
的顶层使用,不可以在<script setup>
的局部变量中引用 - 不可以访问
<script setup>
中定义的其他变量,因为在编译时整个表达式都会被移到外部的函数中 - 语法
// 父组件
<template>
<div>
<helloWorld :msg="msg" title="我是标题"/>
</div>
</template>
<script lang="ts" setup>
import helloWorld from "./components/helloWorld.vue"
import {ref} from "vue";
const msg= ref<String>("我是父组件传过来的值")
</script>
// 子组件
<template>
<h1>{{ msg }}</h1>
</template>
<script setup>
// 第一种写法 不设置默认值
defineProps({
msg?: String,
title: String
})
// 第二种写法 设置默认值
defineProps({
msg: {
type: String,
default: '默认值',
required: false
},
title: {
type: String,
default: '我是标题2',
required: false
},
})
</script>
补充:使用ts写法
<script setup lang="ts">
import { withDefaults } from 'vue';
// 接收Props 字段 定义类型
// 利用ts接口声明类型 分离模式写法定义默认值和定义类型
interface Props {
msg: string,
title?: String
}
const props = withDefaults(defineProps<Props>(),{
msg:'默认值',
title:'默认值'
})
// 组合模式 定义类型&定义默认值
// withDefaults 只能与基于类型的defineProps声明一起使用
const props = withDefaults(defineProps<{
// 非必传
title?: string,
// 必传
msg:string
}>(),{
title:'默认值',
msg:'默认值',
})
</script>
子传父defineEmits
- 用于组件通信中子级组件向父级组件传值,其用来声明emit,其接收内容于emit选项一致
- 只能在
<script setup>
中使用,不需要被导入即可使用,它会在编译<script setup>
语法块时一同编译掉 - 必须在
<script setup>
的顶层使用,不可以在<script setup>
的局部变量中引用 - 语法
// 父组件
<template>
<son @handLer="hand" />
</template>
<script setup>
import son from "./son.vue";
const hand = (v) => {
console.log(v, '我是子组件数据');
};
</script>
// 子组件
<template>
<button @click="fun"> 子传父 </button>
</template>
<script lang="ts" setup>
import { reactive } from 'vue'
// 使用defineEmits语法糖的方法来创建自定义事件
let emigFun = defineEmits(["handLer"]);
const dat = reactive([
{
name: 'weihua',
id: 1,
},
]);
const fun = () => {
// emigFun函数第一个参数是自定义事件名称,第二个参数是需要传递的内容
emigFun("handLer", dat);
}
</script>
祖与后代组件传值 provide 与 inject
- 父组件有一个
provide
选项来提供数据,后代组件有一个inject
选项来开始使用这些数据,允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深, - provide函数:用于给自己的后代组件传递参数
provide(‘car’,car),第1个参数为给你传入的参数起个名字,第2个参数为传入的参数。 - inject函数:用于注入祖组件传递过来的参数
inject(‘car’),形参为传递过来的参数名字 - 语法
// 父组件 index.vue
<template>
<div> <child /></div>
</template>
<script lang="ts" setup>
import child from './components/child.vue'
import { reactive, provide } from 'vue'
// 定义响应式数据
let car = reactive({name:'shaoweihua',age:'18'})
//给自己的后代组件传递数据
provide('car',car)
</script>
// 子组件 child.vue
<template>
<div><son /></div>
</template>
<script lang="ts" setup>
// 引入子子组件
import son from "./son.vue";
</script>
// 子子组件 son.vue
<template>
<div>{{ car.age }}</div>
</template>
<script lang="ts" setup>
import { inject } from 'vue'
let car = inject('car')
console.log(car);
</script>
-
const car = inject(‘car’)
-
其中inject注入的参数返回值也是个Proxy类型的对象,如图打印car
-
provide 与 inject函数 只能用在由“祖组件” -> “子组件”传递参数,不能由子组件 ->
祖组件,且祖组件传递给子组件的参数只能使用,子组件不能修改传递过来的值,否则报错
父取子组件的ref defineExpose -
组件暴露自己的属性以及方法,去供外部使用,常用于父子组件关系
-
在vue3.2中setup 挂载到 script (ref默认是关闭的) 是不能直接使用ref 取到子组件的方法和变量,需要使用defineExpose
-
语法
// 子组件 child.vue 设置defineExpose
<template>
<h1>{{name}}</h1>
</template>
<script lang="ts" setup>
const name = '张三';
const btn = function () {
console.log(111)
}
// 子组件中设置defineExpose等同于将子组件中的方法以及属性全部做出暴漏
// 只需要父级通过ref去获取就可以
defineExpose({ name, btn })
</script>
// 父级组件代码 index.vue
<template>
<child ref="childrenDom"/>
<button @click='handleClick'>点击</button>
</template>
<script lang="ts" setup>
import child from './components/child.vue'
import { ref } from "vue";
// 父级通常使用ref来定义虚拟dom,用来操作子组件
// 那么这个时候打印ref的value值,我们拿不到子组件的属性以及方法
const childrenDom = ref(null)
const handleClick = function () {
console.log(childrenDom.value)
console.log(childrenDom.btn)
}
</script>
Vue3.0 Bus总线mitt实现组件间通信
- Vue2.0的事件总线在3就被弃用了,换成了mitt事件总线
- Vue到3.0之后的Bus的房市变成了使用mitt,2.0是通过创建空的Vue来作为总线
- 使用emit来注册,emit(‘type’, ‘event’),第一个入参是事件名称,第二个入参是参数
- 使用on来监听:on(‘type’, ‘handler’ => { 操作逻辑 }),第一个入参是事件名称需要和上面的emitt事件名称对应,emit和on是成对出现的,一个发起,一个接收
- 2.0之前是$emit, o n , 3.0 去掉了 on, 3.0去掉了 on,3.0去掉了
- 接收方可以是多个组件,只要第一个参数匹配都可以获取到想要的数据
- 语法
- 创建公共的 eventBus 模块 mittBus.ts
import mitt from "mitt";
const mittBus = mitt();
export default mittBus
- 在数据接发送方触发事件
<script lang="ts" setup>
import bus from './mittBus.ts'
import {onUnmounted} from "vue";
// 监听时间方法
const callback = (count:string) => {
console.log(count)
}
mittBus.on('countChange', callback)
// 用完后要清理监听器
onUnmounted(()=>{
mittBus.off('countChange',callback)
})
</script>
- 在数据接收方自定义事件
<script lang="ts" setup>
import {onMounted} from "vue";
import bus from './mittBus.ts'
// 定义赋值
const count = ref('0')
// 加载完后调用 onMounted同mounted
onMounted(()=>{
bus.emit('countChange', count)
})
</script>
- 注册并监听自定义事件:mittBus.on(eventType,callback)
- 触发自定义事件:mittBus.emit(eventType,value)
- 取消所有的mitt事件:mittBus.all.clear()
- 取消单个的mitt事件:mittBus.off(eventType,callback)