组合式API
组合式API(Vue-Composition-API)简称VCA,是Vue3新增加的特性。
前面三篇文章都是基于选项式API(Vue-Options-API,VOA)进行介绍的。是兼容Vue2的写法,使用包含多个选项的对象来描述组件的逻辑,例如data
、methods
、mounted
等。选项所定义的属性都会暴露在函数内部的this
上,它会指向当前的组件实例。
而通过组合式API,可以使用导入的API函数来描述组件逻辑。在单文件组件中,组合式API通常会与<script setup>
搭配使用。这个setup
是一个标识,告诉Vue需要在编译时进行一些处理,让我们可以更简洁地使用组合式API。
例子:
选项式API写法:
<template>
<div @click="changeMsg">{{msg}}</div>
</template>
<script>
export default{
name:"App",
data(){
return{
msg:"hello vue3"
}
},
methods:{
changeMsg(){
this.msg = "hello AiLynn"
}
}
}
</script>
组合式API写法:
<template>
<div @click="changeMsg">{{msg}}</div>
</template>
<script>
import {ref, defineComponent} from "vue"
export default defineComponent({
setup() {
const msg = ref("hello vue3");
const changeMsg = ()=> {
msg.value = "hello PLScript";
}
return {
msg,
changeMsg
}
}
})
</script>
图示:VCA通过组合的方式,把零散在各个data,methods等代码,重新组合,一个功能的代码都放在一起维护,并且这些代码可以单独拆分成函数 。(VCA最早被命名为Vue-Function-API)


setup
在Vue组合式API中,每个组件都必须包含一个setup()
函数。这个函数是在组件初始化时调用,其目的是为组件创建并设置响应式状态,并返回需要再模板中使用的数据和方法。setup()
函数只在组件实例化时执行一次,并返回一个对象。setup()
中不能使用this
,this
指向的是undefined
reactive 和 ref
在Vue组合式API中,使用reactive()
函数接收一个普通的对象传入,把对象数据转化为响应式对象并返回。响应式对象意味着当有数据发生变化时,会自动触发组件重新渲染。
<template>
<div>
<input type="text" v-model="state.mytext">
<button @click="plClickAdd">add</button>
<ul>
<li v-for="(data,index) in state.datalist" :key="data">
{{data}}
<button @click="plClickDel(index)">del</button>
</li>
</ul>
</div>
</template>
<script>
import { reactive } from 'vue';
export default{
setup(){
// 可以有多个reactive
const state = reactive({
mytext:"",
datalist:["pyscript","ailynn","laobai"]
})
const plClickAdd = ()=>{
state.datalist.push(state.mytext)
// 增加成功后清空输入框
state.mytext=""
}
const plClickDel = (index)=>{
state.datalist.splice(index,1)
}
return{
state,
plClickAdd,
plClickDel
}
}
}
</script>
ref()
是创建一个包装式对象,含有一个响应式属性value
,它和reactive()
的差别是,reactive()
没有包装属性value
。
ref()
是在reactive()
基础上进行的二次封装。reative()
是将引用类型数据转换为响应式数据,而ref()
不仅能够将引用类型数据转换为响应式数据,还可以将基本类型转换为响应式数据,并可以获取DOM元素。
JavaScript的基本数据类型:Number
、String
、Boolean
、Null
、Undefined
、Symbol
;引用数据类型:Object
(除了基本数据类型以外的都是对象)
<template>
<div>
app
{{myname}}
<input type="text" ref="myinput">
<button @click="plClick">click</button>
</div>
</template>
<script>
import {ref} from "vue"
export default {
setup(){
// ref(基本类型)
const myname = ref("AiLynn") // new Proxy({value:"AiLynn"})
const myinput = ref(null)
const plClick = ()=>{
myname.value = "PLScript"
console.log(myinput.value)
console.log(myinput.value.value)
}
return {
myname,
myinput,
plClick
}
}
}
</script>
将上面reactive()
的代码改写为ref()
代码,如下:
<template>
<div>
<input type="text" v-model="mytext">
<button @click="plClickAdd">add</button>
<ul>
<li v-for="(data,index) in datalist" :key="data">
{{data}}
<button @click="plClickDel(index)">del</button>
</li>
</ul>
</div>
</template>
<script>
import { reactive } from 'vue';
import { ref } from 'vue'
export default{
setup(){
// 可以有多个reactive
// const state = reactive({
// mytext:"",
// datalist:["pyscript","ailynn","laobai"]
// })
const mytext = ref("")
const datalist = ref(["pyscript","ailynn","laobai"])
const plClickAdd = ()=>{
// state.datalist.push(state.mytext)
datalist.value.push(mytext.value)
// 增加成功后清空输入框
// state.mytext=""
mytext.value=""
}
const plClickDel = (index)=>{
// state.datalist.splice(index,1)
datalist.value.splice(index,1)
}
return{
mytext,
datalist,
plClickAdd,
plClickDel
}
}
}
</script>
toRefs 和 toRef
toRef
函数的作用是将对象中的某个属性转换成响应式数据;
toRefs
函数的作用是将对象中的所有属性转换成响应式数据;
<template>
<div>
<input type="text" v-model="mytext">
<button @click="plClickAdd">add</button>
<ul>
<li v-for="(data,index) in datalist" :key="data">
{{data}}
<button @click="plClickDel(index)">del</button>
</li>
</ul>
</div>
</template>
<script>
import { reactive,toRef,toRefs } from 'vue';
export default{
setup(){
// 可以有多个reactive
const state = reactive({
mytext:"",
datalist:["pyscript","ailynn","laobai"]
})
const plClickAdd = ()=>{
state.datalist.push(state.mytext)
// 增加成功后清空输入框
state.mytext=""
}
const plClickDel = (index)=>{
state.datalist.splice(index,1)
}
return{
// mytext:toRef(state,"mytext"),
...toRefs(state),
state,
plClickAdd,
plClickDel
}
}
}
</script>
Computed
使用reactive()
组合式API书写Computed()
<template>
<div>
<input type="text" v-model="mytext">
<ul>
<li v-for="data in computedList" :key="data">
{{data}}
</li>
</ul>
</div>
</template>
<script>
import { reactive,toRefs,computed } from "vue"
export default {
setup(){
const state = reactive({
mytext:"",
datalist:["aaa","bba","abb","abc","bcc","acc","aac","aab","bbc","bbb"]
})
const computedList = computed(()=>
state.datalist.filter(item=>item.includes(state.mytext))
)
return {
...toRefs(state),
computedList
}
}
}
</script>
上面代码中已经实现了组合式API模式,但可以看到代码只是有选项式API重新组合了一下,代码层面上并没显得更高效。接下来将实现search功能的代码抽象成方法。
<template>
<div>
<input type="text" v-model="mytext">
<ul>
<li v-for="data in computedList" :key="data">
{{data}}
</li>
</ul>
</div>
</template>
<script>
import { reactive,ref,computed } from "vue"
function UseSearch(state) {
const mytext = ref("")
const computedList = computed(()=>
state.datalist.filter(item=>item.includes(mytext.value))
)
return {
mytext,
computedList
}
}
export default {
setup(){
const state = reactive({
datalist:[]
// mytext:"",
// datalist:["aaa","bba","abb","abc","bcc","acc","aac","aab","bbc","bbb"]
})
setTimeout(() => {
state.datalist=["aaa","bba","abb","abc","bcc","acc","aac","aab","bbc","bbb"]
},2000)
const {mytext,computedList} = UseSearch(state)
return {
// mytext,
// computedList
...UseSearch
}
}
}
</script>
function UseSearch()
实现了搜索的逻辑,接下来将UseSearch()
单独提取到一个js文件中,这样就提高了代码的复用性,在其他用到搜索的地方可以直接引入即可。(用户自定义的函数名前通常写上Use
)
import { ref,computed } from "vue"
function UseSearch(state) {
const mytext = ref("")
const computedList = computed(()=>
state.datalist.filter(item=>item.includes(mytext.value))
)
return {
mytext,
computedList
}
}
export default UseSearch
<template>
<div>
<input type="text" v-model="mytext">
<ul>
<li v-for="data in computedList" :key="data">
{{data}}
</li>
</ul>
</div>
</template>
<script>
import { reactive } from "vue"
import UseSearch from "./search"
export default {
setup(){
const state = reactive({
datalist:[]
// mytext:"",
// datalist:["aaa","bba","abb","abc","bcc","acc","aac","aab","bbc","bbb"]
})
setTimeout(() => {
state.datalist = ["aaa","bba","abb","abc","bcc","acc","aac","aab","bbc","bbb"]
}, 2000)
// const {mytext,computedList} = UseSearch(state)
return {
// mytext,
// computedList
...UseSearch(state)
}
}
}
</script>
上方是通过reactive()
实现的,下面再使用ref()
进行实现
<template>
<div>
<input type="text" v-model="mytext">
<ul>
<li v-for="data in computedList" :key="data">
{{data}}
</li>
</ul>
</div>
</template>
<script>
import { ref } from "vue"
import UseSearch from "./search"
export default {
setup(){
// const state = reactive({
// datalist:[]
// })
const datalist = ref([])
setTimeout(() => {
datalist.value = ["aaa","bba","abb","abc","bcc","acc","aac","aab","bbc","bbb"]
}, 2000)
// const {mytext,computedList} = UseSearch(state)
return {
// mytext,
// computedList
...UseSearch(datalist)
}
}
}
</script>
import { ref,computed } from "vue"
function UseSearch(datalist) {
const mytext = ref("")
const computedList = computed(()=>
datalist.value.filter(item=>item.includes(mytext.value))
)
return {
mytext,
computedList
}
}
export default UseSearch
watch
watch
具有一定的惰性,第一次页面展示的时候不会执行,只有数据变化的时候才会执行。若要第一次也展示数据,需要增加{immediate:true}
。watch
可以通过参数获取到当前值和原始值watch(mytext,(newValue,oldValue)
。
<template>
<div>
<input type="text" v-model="mytext">
<select v-model="select">
<option value="aaa">aaa</option>
<option value="bbb">bbb</option>
<option value="ccc">ccc</option>
</select>
</div>
</template>
<script>
import { ref, watch } from 'vue';
export default {
setup(){
const mytext = ref("")
const select = ref("aaa")
// 写法1
// watch(mytext,(newValue,oldValue)=>{
// console.log("同步/异步","ajax",newValue,oldValue)
// })
// 写法2
// watch(()=>mytext.value,(newValue,oldValue)=>{
// console.log("同步/异步","ajax",newValue,oldValue)
// })
// 多个数据源侦听
// watch([mytext,select],(newValue,oldValue)=>{
// console.log("同步/异步","ajax",newValue,oldValue)
// })
watch([mytext,select],(newValue,oldValue)=>{
console.log("同步/异步","ajax",newValue,oldValue)
},{immediate:true})
return {
mytext,
select
}
}
}
</script>
props 和 emit
props
子组件声明接收 props:["attr1","attr2"]
emit
子组件触发自定义事件$emit("自定义事件名")
App.vue
<template>
<div>
<Navbar title="首页" :left="true" @leftevent="handleLeft"/>
<Navbar title="影院" :left="false"/>
</div>
</template>
<script>
import Navbar from './Navbar.vue';
export default {
components:{
Navbar
},
setup(){
const handleLeft = ()=>{
console.log("left button")
}
return {
handleLeft
}
}
}
</script>
Navbar.vue
<template>
<div>
<button v-show="left" @click="handleClick">返回</button>
{{ computedTitle }}
<button>首页</button>
</div>
</template>
<script>
import { computed } from 'vue';
export default {
props:{
title:String,
left:Boolean
},
// computed:{
// computedTitle(){
// return this.title + "-AiLynn"
// }
// }
setup(props,{emit}){
const computedTitle = computed(()=>props.title+"-AiLynn")
const handleClick = ()=> {
console.log("click")
emit("leftevent")
}
return {
computedTitle,
handleClick
}
}
}
</script>
其中,setup(props,{emit})
的第一参数是传递属性的对象集合,第二个参数是将需要用到的属性对象直接通过{}
进行解构进行使用。两种方式都可以使用。

VCA生命周期
选项式API的生命周期与组合式API的生命周期对比:
VOA(Vue2/Vue3) | VCA(Vue3) |
---|---|
befoceCreate | setup |
created | setup |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroy(beforeUnmount) | onBeforeUnmount |
destroyed(unmounted) | onUnmounted |
<template>
<div>
div - {{ a }}
<button @click="plClick">click</button>
</div>
</template>
<script>
import { ref,onBeforeMount, onBeforeUpdate, onMounted, onUpdated,nextTick } from 'vue';
export default {
setup(){
const a = ref("AiLynn")
onBeforeMount(()=>{
console.log("dom创建之前调用")
})
onMounted(()=>{
console.log("订阅、ajax、dom创建后,初始化实例")
})
onBeforeUpdate(()=>{
console.log("更新之前")
})
onUpdated(()=>{
console.log("更新之后")
})
const plClick = ()=>{
a.value = "PLScript"
nextTick(()=>{
console.log("nextTick")
})
}
return {
a,
plClick
}
}
}
</script>
script setup语法糖
<script setup>
是在单文件组件(SFC)中使用组合式API的编译时语法糖。当同时使用SFC与组合式API时该语法糖是默认推荐。相比于普通的<script>
语法,它具有更多优势:
- 更少的样板内容,更简洁的代码;
- 能够使用纯
TypeScript
声明props
和自定义事件; - 更好的运行时性能(其模板会被编译成同一作用域内的渲染函数,避免了渲染上下文代理对象);
- 更好的IDE类型推导性能(减少了语言服务器从代码中抽取类型的工作);
主组件App.vue
<template>
<div>
app - {{message}}
<button @click="plClick">click</button>
<div>
{{state.myname}}-{{state.myage}}
</div>
<div>
{{myname}}___{{myage}}
</div>
<div>
{{computedName}}
</div>
<Child title="title1111" @right="plRightClick"/>
</div>
</template>
<script setup>
import { ref,reactive,toRefs, computed } from "vue"
import Child from "./Child.vue"
const message = ref("hello AiLynn")
const state = reactive({
myname:"laobai",
myage:18
})
const {myname,myage} = {...toRefs(state)}
const plClick = ()=>{
console.log(message)
message.value = "hello PLScript"
myage.value=81
}
const computedName = computed(()=>myname.value.substring(0,1).toUpperCase()+myname.value.substring(1))
const plRightClick = (value)=>{
console.log("app",value)
}
</script>
子组件Child.vue
<template>
<div>
Child-{{title}}
<button @click="plClickBack">返回</button>
</div>
</template>
<script setup>
// import { defineProps } from "vue"
// 父传子,子接收
// 接收父组件传过来的值,使用defineProps进行接收。新版本中已不用import了
const props = defineProps({
title:{
type:String,
default:"title0000"
}
})
// 子传父,子发送
// 定义要从子组件返给父组件的事件集合,使用defineEmits,同样新版本中已经不用import了
const emit = defineEmits(["right"])
const plClickBack = ()=>{
emit("right","从子组件返回的数据")
}
console.log(props.title)
</script>