一、Composition API
Options API 的弊端
Options API 的一大特点就是在对应的属性中编写对应的功能模块,比如在data属性中定义数据、methods中定义方法、computed属性中定义计算属性等等...但是这种代码有几个很大的弊端:
- 当我们实现某一个功能模块时,这个功能模块对应的代码逻辑会被拆分到各个属性中;
- 当我们组件变得更大、更复杂时,逻辑关注点的列表就会增长,那么同一个功能的逻辑就会被拆分的很分散;
- 尤其对于那些一开始没有编写这些组件的人来说,这个组件的代码是难以阅读和理解的(阅读组件的其他人)
认识Composition API
如果我们能将同一个逻辑关注点相关的代码收集在一起会更好,这就是Composition API想要做的事情。
setup函数
为了开始使用Composition API,我们需要有一个可以实际使用它编写代码的地方,这就是setup函数,setup其实就是组件的另一个属性,只不过这个属性强大到我们可以用它来替代之前所编写的大部分其它属性,比如methos、computed、watch等等...
setup的参数
setup函数接收两个参数,props和context
props
父组件传过来的属性,props.xxx使用就好了
context
- attrs:所有的非prop的attribute
- slots:父组件传过来的插槽
- emit:当我们组件内部需要发出事件时会用到emit,因为我们不能访问this所以不能通 过 this.$emit来发出事件
setup的返回值
setup的返回值可以在模版template中使用,用来代替Vue2的data选项
Reactive API
如果想在setup中定义响应式数据的话,我们可以使用reactive的函数,但是只能传入对象或者数组类型:
<script>
import { reactive } from "vue"
export default{
setup(){
const obj= reactive({
name:"张三"
})
return{
obj
}
}
}
</script>
Ref API
reactive API 对传入的类型是有限制的,它要求我们必须传入的是一个对象或者数组类型。如果我们传入一个基本数据类型(String、Number、Boolean等)会报一个警告。
这个时候Vue3给我们提供了另一个API:ref API,ref会返回一个响应式对象,它内部的值实在ref的value属性中被维护的:
<script>
import { ref } from "vue"
export default{
setup(){
const name = ref("张三")
return{
name
}
}
}
</script>
这里有几个注意事项:
- 在模板中使用ref的值时,Vue会自动帮助我们进行解包操作,所以我们并不需要在模板中通过ref.value的方式来使用;
- 但是在setup函数内部,它依然是一个ref引用,所以对其进行操作时我们需要通过ref.value的方式来更新它的值;
- 模板中的解包是浅层的解包,如果ref对象放到一个object里面,那么模板中就需要再.value才能使用,例如下面的代码:
<template> <p> {{ obj.name.value }} </p> </template> <script> import { ref } from "vue" export default{ setup(){ const name = ref("张三"); const obj = { name } return{ name } } } </script>
readonly
我们通过reactive和ref可以获取到一个响应式对象,但是某些情况下我们希望传入到其它组件的这个响应式对象只能被使用但是不能被修改。readonly会返回原生对象的只读代理,劫持了它的set方法。
isProxy
检查对象是否是由reactive或readonly创建的proxy
isReactive
检查对象是否是由reactive创建的响应式代理
isReadonly
检查对象是否是由readonly创建的只读代理
toRaw
返回reactive或readonly代理的原始对象
shallowReactive
创建一个浅层的响应式代理
shallowReadonly
创建一个proxy,使其自身的property为只读,但是深层对象还是可读、可写的
toRefs
将reactive返回的对象中的属性都转成ref
toRef
将reactive返回的对象中的一个属性都转成ref
unref
获取ref中的value
isRef
检查对象是否是由ref创建的响应式代理
shallowRef
创建一个浅层的ref对象
triggerRef
手动触发和shallowRef相关联的副作用
computed
在Vue2中我们是使用computed选项来完成计算操作的,而在Composition API 中我们可以在setup函数中使用computed方法来编写一个计算属性,它有两种用法:
- 传入一个getter函数
<script> import { ref,computed } from "vue" const firstName = ref("张") const lastName = ref("三") const fullName = computed(()=> firstName.value + lastName.value) return { fullName } </script>
- 传入一个对象,对象中需要包含getter和setter
<script> import { ref,computed } from "vue" const firstName = ref("张") const lastName = ref("三") const fullName = computed({ get:()=> firstName.value + lastName.value, set(newValue){ ... } }) return { fullName } </script>
watchEffect/watch
在Vue2中我们是使用watch选项来侦听data或者props的数据变化,而在Composition API 中我们可以在setup函数中使用watchEffect和watch方法来完成响应式数据的侦听。
watchEffect用于自动收集响应式数据的依赖
<script>
import { ref,watchEffect} from "vue"
const name = ref("张三")
const fullName = watchEffect(()=> {
console.log(name.value)
})
return {
fullName
}
</script>
清除watchEffect的副作用
什么是清除副作用的?比如在开发中我们需要在侦听函数中请求数网络数据,但是在网络请求还没有完成的时候我们停止了侦听器或者侦听器里的侦听函数再次被执行了,那么上一次的网络请求就应该被取消掉。
在我们给watchEffect传入的函数被回调时,可以获取到一个参数:onInvalidate,这个参数是个回调函数,我们可以在这个回调函数中取消掉上一次的网络请求:
<script>
import { ref,watchEffect} from "vue"
const name = ref("张三")
const fullName = watchEffect((onInvalidate)=> {
console.log(name.value);
// 模拟网络请求
const timer = setTimeout(()=>{
console.log('2s后执行的操作')
},2000);
onInvalidate(()=>{
clearTimeout(timer);
})
})
return {
fullName
}
</script>
调整watchEffect的执行时机
因为watchEffect会立即执行里面的侦听函数,当这个时候去获取DOM的话值就是NULL,所以需要去调整watchEffect的执行时机,也就是要用到watchEffect传入的第二个参数:
<script>
import { ref,watchEffect } from "vue"
const name = ref("张三")
const fullName = watchEffect(()=> {
console.log(name.value)
},{
flush:"post" // pre(默认)|post|sync强制触发(低效的)
})
return {
fullName
}
</script>
watch需要手动指定侦听对象
默认情况下watch是惰性的(只有当被侦听的数据发生变化时才会执行回调)
侦听单个数据源
一个getter函数,但是该getter必须引用可响应式的对象(比如reactive或者ref)
<script>
import { reactive , watch } from "vue"
const info = reactive({name:"张三"})
watch(()=>info.name,(newValue,oldValue)=> {
console.log(newValue)
console.log(oldValue)
})
return {
info
}
</script>
也可以是一个可响应式的对象,ref或者reactive
<script>
import { reactive , watch } from "vue"
const info = reactive({name:"张三"})
watch(info,(newValue,oldValue)=> {
console.log(newValue)
console.log(oldValue)
})
return {
info
}
</script>
与watchEffect相比较,watch允许我们:
1.第一次不会执行
2.更具体的指明监听哪些数据
3.访问侦听数据变化的新旧值
侦听多个数据源
<script>
import { reactive , watch } from "vue"
const info = reactive({name:"张三"})
// 方法一
watch([info,info.name],(newValue,oldValue)=> {
console.log(newValue)
console.log(oldValue)
})
//方法二
watch(()=>({...info}),(newValue,oldValue)=> {
console.log(newValue)
console.log(oldValue)
})
return {
info
}
</script>
二、新的生命周期钩子
-
去掉了vue2.0中的 beforeCreate 和 created 两个阶段,新增了一个
setup
。执行setup
时,组件实例尚未被创建。 -
每个生命周期函数必须导入才可以使用,并且所有生命周期函数需要统一放在
setup
里使用。 -
destroyed 销毁后被重命名为 unmounted卸载后;beforeDestroy销毁前生命周期选项被重命名为 beforeUnmount卸载前。
三、新的组件
片段(Fragment)
Vue2: 组件必须有一个根标签
<template>
<div>
<span></span>
<span></span>
</div>
</template>
Vue3: 组件可以没有根标签, 可以直接写多个根节点,内部会将多个标签包含在一个Fragment虚拟元素中
<template>
<span></span>
<span></span>
</template>
Teleport
Teleport 就像是一个「任意门」,将包裹组件html结构传送到任何指定的地方。
例如我们日常开发中经常会使用到弹窗组件,Dialog组件会被渲染到一层层子组件内部,处理样式、定位都变得十分困难。这时我们希望将组件挂载在body上面,来更方便的控制Dialog的样式。简单来说,我们既希望继续在组件内部使用Dialog,又希望渲染的 DOM 结构不嵌套在组件内部的 DOM 中。就可以用到<Teleport>
, 它可以在「不改变组件内部元素父子关系」的情况下,建立一个传送门将Dialog渲染的内容传送到body上面。
<teleport to= body >
<div v-if= isShow class= dialog >
<div class= dialog >
<h3>弹窗</h3>
<button @click= isShow = false >关闭弹窗</button>
</div>
</div>
</teleport>
Suspense
等待异步组件时渲染一些额外内容,让应用有更好的用户体验 它提供两个template slot, 刚开始会渲染一个 fallback插槽下的内容, 直到到达某个条件后才会渲染 default 插槽的正式内容, 通过使用Suspense组件进行展示异步渲染更加简单。
<template>
<div class= app >
<h3>我是App组件</h3>
<Suspense>
<template v-slot:default>
<NewSuspense/>
</template>
<template v-slot:fallback>
<h3>加载中.....</h3>
</template>
</Suspense>
</div>
</template>