一、Composition API 的简单介绍
Composition API 也叫组合式API,是Vue3.x的新特性。
通过创建Vue组件,我们可以将接口的可重复部分及其功能提取到可重用的代码段中。仅此一项就可以使我们的应用程序在可维护性和灵活性方面走得更远。然而,我们的经验已经证明,光靠这一点是不够的,尤其是当你的应用程序变得非常大的时候—想想几百个组件。在处理如此大的应用程序时,共享和重用代码变得尤为重要。
通俗的讲:
没有Composition API 之前的Vue相关业务的代码需要配置到option的特定区域,中小型项目是没问题的,但是在大型项目中会导致后期的维护性比较复杂,同时代码可复用性不高。Vue3.x中的Composition API就是为了解决这个问题而生的。
Composition API 提供了一下几个函数:
- setup (所有组合式API的代码都需写到这个方法中)
- ref (定义响应式数据的方法–主要用于string、boolean、number、array类型)
- reactive (定义响应式数据的方法–主要用于object)
- watchEffect (监听数据变化的方法)
- watch (监听数据变化的方法)
- computed (计算属性的方法)
- toRefs (结构响应式对象数据的)
- 生命周期的hooks
二、setup组件选项
新的setup组件选项在创建组件之前执行,一旦props被解析,并充当合成API的入口点。
提示:
由于在执行setup时尚未创建组件实例,因此在setup选项中没有this。这意味着,除了props之外,你将无法访问组件中声明的任何属性—本地状态、计算属性或方法。
使用setup函数时,他将接受两个参数:
- props
- context
使用方法:
引入vue中的api函数
import {ref,reactive, toRefs, computed} from "vue"
定义setup方法,并在setup中return
setup() {
// ref、reactive定义响应式数据
// ref:定义String,Number,Boolean,Array
// reactive:定义Object
let title = ref("我是一个标题");
let userinfo = reactive({
username: "张三",
age: 20
});
// 获取ref里面的数据
let getTitle = () => {
// 获取属性时,返回的是一个对象
console.log(title.value);
};
// ...属性
// ...对象
// ...方法
return {
// 将定义的数据、方法暴露出去
title,
getTitle,
}
}
使用toRefs结构对象
引入vue中的toRefs
```javascript
import {toRefs} from "vue"
在setup中使用…toRefs结构对象
setup() {
let article = reactive({
description: "我是一个新闻",
click: 20
});
return {
// 将定义的数据、方法暴露出去
...toRefs(article),// 使用toRefs结构对象
}
}
完整示例:
<!-- -->
<template>
<div>
Home组件
<br>
<button @click="run">执行run</button>
<br>
<hr>
<h4>使用Composition API定义的数据</h4>
{{ title }}
<br>
{{ userinfo.username }}---{{ userinfo.age }}
<br>
<button @click="setTitle">设置title</button>
<br>
<button @click="getTitle">获取title</button>
<br>
<button @click="setUserName">设置username</button>
<br>
<button @click="getUserName">获取username</button>
<br>
<h5>双向数据绑定:</h5>
<input type="text" v-model="title">
<br>
<input type="text" v-model="userinfo.username">
</div>
<h5>结构响应式对象数据(直接使用对象中的属性输出)</h5>
description:{{ description }}
<br>
<input type="text" v-model="description">
</template>
<script>
import {ref, reactive, toRefs} from "vue";// 引入Composition API
export default {
setup() {
// ref、reactive定义响应式数据
// ref:定义String,Number,Boolean,Array
// reactive:定义Object
let title = ref("我是一个标题");
let userinfo = reactive({
username: "张三",
age: 20
});
let article = reactive({
description: "我是一个新闻",
click: 20
});
// 获取ref里面的数据
let getTitle = () => {
// 获取属性时,返回的是一个对象
console.log(title.value);
};
// 修改ref里面的数据
let setTitle = () => {
title.value = "修改后的ref里面的title";
}
// 获取reactive里面定义的数据
let getUserName = () => {
console.log(userinfo.username);
};
// 修改reactive里面的的数据
let setUserName = () => {
userinfo.username = "zws";
};
return {
// 将定义的数据、方法暴露出去
title,
userinfo,
getTitle,
setTitle,
getUserName,
setUserName,
// ...article,// 三点运算符合并对象属性,失去了双向绑定的响应式模式
...toRefs(article),// 使用toRefs结构对象
}
},
data() {
return {
msg: "我是home组件的msg",
};
},
methods: {
run() {
console.log("home的run方法");
}
},
};
</script>
<style scoped>
</style>
2.1 Props
setup函数中的第一个参数时props。正如在一个标准组件中所期望的那样,setup函数中的props是响应式的,当传入新的prop时,它将被更新。
export default{
props:{
title:String
},
setup(props){
console.log(props.title);
}
}
注意:
但是因为props时响应式的,你不能使用ES6结构,因为他会消除prop的响应性。
如果需要结构prop,可以通过使用setup函数中的toRefs来安全的完成此操作。
props示例:
//父组件Father.vue
<template>
<div>父组件</div>
<hr>
<v-child :title="title"></v-child>
</template>
<script>
import{ref} from "vue";
import Child from "./Child";
export default {
name: "Father.vue",
setup(){
let title=ref("我是父组件传来的title");
return{
title
}
},
components:{
"v-child":Child
}
}
</script>
<style scoped>
</style>
子组件通过props接收,在setup中使用
<template>
<div>子组件</div>
</template>
<script>
export default {
name: "Child",
props: ["title"],
setup(props) {
console.log(props);
}
}
</script>
<style scoped>
</style>
三、computed计算属性
与vue2.x用法相同,只是写法上有区别,需要引入vue中的computed
import {computed} from "vue"
在setup中定义即可
<template>
<h2>获取用户信息</h2>
<input type="text" v-model="firstName" placeholder="firstName"/>
<br>
<input type="text" v-model="lastName" placeholder="lastName"/>
<br>
{{ firstName }} + {{ lastName }} = {{ fullName }}
</template>
<script>
import {reactive, toRefs, computed} from "vue"
export default {
name: "Login.vue",
setup() {
let userInfo = reactive({
firstName: "",
lastName: "",
});
let fullName = computed(() => {
return userInfo.firstName + userInfo.lastName;
});
return {
...toRefs(userInfo),
fullName,
}
}
}
</script>
<style lang="scss" scoped>
h2 {
text-align: center;
}
</style>
四、readonly "深层"的只读代理
传入一个对象(响应式或普通)或ref,返回一个原始对象的只读代理。一个只读的代理是"深层的",对象内部任何嵌套的属性也都是只读的。
使用readonly将响应式数据改为非响应式数据,实际应用中用的很少,了解一下
<template>
<h2>获取用户信息</h2>
<input type="text" v-model="firstName" placeholder="firstName"/>
<br>
<input type="text" v-model="lastName" placeholder="lastName"/>
<br>
{{ firstName }} + {{ lastName }} = {{ fullName }}
<h2>原始对象</h2>
<input type="text" v-model="obj.username">
<br>
{{obj.username}}
</template>
<script>
import {reactive, toRefs, computed,readonly} from "vue"
export default {
name: "Login.vue",
setup() {
// 原始对象,非响应式数据
let obj = {
username:"张三",
age:"18",
};
// 响应式数据
let userInfo = reactive({
firstName: "",
lastName: "",
});
// 将响应式数据使用readonly改为非响应式数据
userInfo=readonly(userInfo);
let fullName = computed(() => {
return userInfo.firstName + userInfo.lastName;
});
return {
obj,
...toRefs(userInfo),
fullName,
}
}
}
</script>
<style lang="scss" scoped>
h2 {
text-align: center;
}
</style>
运行,userInfo对象中的数据无法实现双向绑定
控制台输出警告:
reactivity.esm-bundler.js?a1e9:337 Set operation on key "firstName" failed: target is readonly.
Proxy {firstName: "", lastName: ""}
reactivity.esm-bundler.js?a1e9:337 Set operation on key "lastName" failed: target is readonly.
Proxy {firstName: "", lastName: ""}
五、watchEffect
在响应式的耿总其依赖时立即运行一个函数,并在更改依赖项时重新运行它。
六、watch、watch与watchEffect区别
对比watchEffect:
- 刚开始运行的时候会触发一次监听器回调
- 更明确哪些状态改变会触发监听器重新运行;
,watch允许我们:
- 懒执行,也就是说仅在监听的源变更时才执行回调;
- 只能监听对象,不能具体到某个属性
- 访问监听状态变化前后的值
更明确哪些状态的改变会触发监听器重新运行:
示例:
<template>
<div>
Search
<br>
num={{num}}
<br>
count={{count}}
<br>
userinfo.age={{age}}
</div>
</template>
<script>
// 1.watchEffect
/*
import {reactive, toRefs, watchEffect} from "vue"
export default {
name: "Search",
setup() {
let data = reactive({
num: 1,
count:1,
});
watchEffect(() => {
// 刚加载的时候执行一次,只有改不num时才会执行
console.log(`num=${data.num}`);
});
setInterval(() => {
data.num++;
},1000);
return {
...toRefs(data),
}
},
}*/
//2.watch
import {reactive, toRefs, watch} from "vue"
export default {
name: "Search",
setup() {
let data = reactive({
num: 1,
count:1,
});
let userinfo=reactive({
username:"张三",
age:10
});
watch(data,() => {
console.log(`num=${data.num}`);
});
setInterval(() => {
data.count++;
// userinfo.age++
},1000);
return {
...toRefs(data),
...toRefs(userinfo),
}
},
}
</script>
<style scoped>
</style>
watch可以访问监听状态变化前后的值
<template>
<div>
<input type="text" v-model="keyword">
<br>
{{ keyword }}
</div>
</template>
<script>
import {
ref,
watch
} from "vue"
export default {
name: "Search",
setup() {
let keyword = ref("");
watch(keyword, (newData, oldData) => {
// 打印出变化前的值和当前值
console.log(newData, oldData);
});
return {
keyword,
}
},
}
</script>
<style scoped>
</style>
七、Composition API声明周期钩子
你可以通过在声明周期钩子前面加“on”来访问组件的声明周期钩子。
下表包含如何在setup()内部调用生命周期钩子:
选项式API(Vue2.x) | Hook inside setup |
---|---|
beforeCreate | 不需要 |
created | 不需要 |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
因为setup式围绕beforeCreate和created生命周期钩子运行的,所以不需要显示的定义他们。换句话说在这些钩子中编写任何代码都应该直接在setup函数中编写。
<template>
<div>
<input type="text" v-model="keyword">
<br>
{{ keyword }}
</div>
</template>
<script>
import {
ref,
watch,
onMounted,
onUpdated
} from "vue"
export default {
name: "Search",
setup() {
console.log("setup");
let keyword = ref("");
watch(keyword, (newData, oldData) => {
// 打印出变化前的值和当前值
console.log(newData, oldData);
});
onMounted(() => {
console.log("onMounted")
});
onUpdated(() => {
console.log("onUpdated")
});
return {
keyword,
}
},
beforeCreate() {
console.log("beforeCreate");
}
}
</script>
<style scoped>
</style>