目录
为什么要引入 组合式API?
下面是一个大型组件的示例,其中逻辑关注点按颜色进行分组。
问题:
- 这种碎片化使得理解和维护复杂组件变得困难。
- 选项的分离掩盖了潜在的逻辑问题。
- 在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块。
优化点:
如果能够将同一个逻辑关注点相关代码收集在一起会更好。
而这正是组合式 API 使我们能够做到的。
setup
组件选项
在
setup
中你应该避免使用this
原因:
它不会找到组件实例。
setup
的调用发生在data
property、computed
property 或methods
被解析之前,所以它们无法在setup
中被获取。
简单使用
代码
<script>
export default{
data(){
return{
}
},
//组合式API 将同一个逻辑关注点相关代码收集起来
setup(){
//组件被创建之前执行 不能使用this
const msg='hello';
console.log(msg);
}
}
</script>
<template>
</template>
<style>
</style>
效果
setup中 非响应式变量 带 ref
的响应式变量
理解
ref
接收参数并将其包裹在一个带有value
property 的对象中返回,然后可以使用该 property 访问或更改响应式变量的值
ref
为我们的值创建了一个响应式引用。注意:从
setup
返回的 refs 在模板中访问时是被自动浅解包的,因此不应在模板中使用.value 但是函数中需要.value
代码
<script>
import { ref } from '@vue/reactivity';
export default{
data(){
return{
}
},
//组合式API 将同一个逻辑关注点相关代码收集起来
setup(){
//组件被创建之前执行 不能使用this
//msg的代码逻辑
//没有响应式
let msg='hello';
function changeMsg(){
msg='你好呀';
console.log(msg);
}
//通过ref定义响应式变量
//counter的逻辑代码 counter 为一个对象 静态 可变对象中的值
const counter=ref(0);
//ref() 返回带有value属性的对象
function changeCounter(){
counter.value='16';
console.log(counter.value);
}
//将函数 属性 暴露出去 可被其他使用
return {
msg,
changeMsg,
counter,
changeCounter
}
}
}
</script>
<template>
<h2>{{msg}}</h2>
<button @click="changeMsg">非响应式====改变msg</button>
<br>
<!-- 模块会自动解析value值 如果填写counter.value 样式会没有 -->
<h2>{{counter}}</h2>
<button @click="changeCounter">响应式====改变counter</button>
</template>
<style>
</style>
效果
点击按钮前
点击按钮后
watch
响应式更改
- 一个想要侦听的响应式引用或 getter 函数
- 一个回调
- 可选的配置选项
代码
<script>
import { reactive, ref } from '@vue/reactivity';
import { watch,watchEffect } from '@vue/runtime-core';
export default{
data(){
return{
}
},
//组合式API 将同一个逻辑关注点相关代码收集起来
setup(){
const counter=ref(0);
function changeCounter(){
counter.value='16';
}
//watch(侦听的响应式引用,回调函数)
watch(counter,(newValue,oldValue)=>{
console.log("new counter======{}==",newValue);
console.log("old counter======{}==",oldValue);
});
const user=reactive({
name:"张三"
})
function changeUserName(){
user.name="王五";
}
watch(user,(newValue,oldValue)=>{
console.log("new user======{}==",newValue);
console.log("old user======{}==",oldValue);
});
//watchEffect(回调函数) 注意:不需要指定监听的属性 组件初始化的时候会执行一次回调函数 自动收集依赖
watchEffect(()=>{
console.log(user.name);
})
//将函数 属性 暴露出去 可被其他使用
return {
counter,
user,
changeCounter,
changeUserName
}
/*
watch和watchEffect的区别:
1.watchEffect不需要指定监听的属性 自动收集依赖 只要在回调中引用到了响应式的属性 这些属性发生改变 回调就会执行
watch 只能侦听指定的属性 做出回调函数的执行 可以侦听多个
2.watch可以获取到新值和旧值 watchEffect获取不到
3.watchEffect在组件初始化的时候就会自动执行一次 用来收集依赖 watch不需要 一开始就指定了
*/
}
}
</script>
<template>
<h2>{{counter}}</h2>
<button @click="changeCounter">改变counter</button>
<br>
<!-- 模块会自动解析value值 如果填写counter.value 样式会没有 -->
<h2>{{user.name}}</h2>
<button @click="changeUserName">改变userName</button>
</template>
<style>
</style>
结果
watch和watchEffect的区别:
1.watchEffect不需要指定监听的属性 自动收集依赖 只要在回调中引用到了响应式的属性 这些属性发生改变 回调就会执行
watch 只能侦听指定的属性 做出回调函数的执行 可以侦听多个
2.watch可以获取到新值和旧值 watchEffect获取不到
3.watchEffect在组件初始化的时候就会自动执行一次 用来收集依赖 watch不需要 一开始就指定了
独立的 computed
属性
computed
函数传递了第一个参数,它是一个类似 getter 的回调函数,输出的是一个只读的响应式引用。为了访问新创建的计算变量的 value,我们需要像ref
一样使用.value
property。
简单使用
<script>
import { reactive, ref } from '@vue/reactivity';
import { computed} from '@vue/runtime-core';
export default{
data(){
return{
}
},
//组合式API 将同一个逻辑关注点相关代码收集起来
setup(){
const msg=ref("hello world");
const reverseMsg = computed(()=>{
//返回一个带value的对象
return msg.value.split("").reverse().join("");
})
console.log(reverseMsg);
const user=reactive({
name:"张三",
reverseMsg: computed(()=>{
//返回一个带value的对象
return msg.value.split("").reverse().join("");
})
})
console.log(user.reverseMsg);
//将函数 属性 暴露出去 可被其他使用
return {
counter,
user,
changeCounter,
changeUserName
}
}
}
</script>
<template>
</template>
<style>
</style>
结果
生命周期钩子
下表包含如何在 setup () 内部调用生命周期钩子:
选项式 API | Hook inside setup |
---|---|
beforeCreate | Not needed* |
created | Not needed* |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
activated | onActivated |
deactivated | onDeactivated |
因为
setup
是围绕beforeCreate
和created
生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在setup
函数中编写。这些函数接受一个回调函数
简单使用
<script>
import { onBeforeMount, onMounted} from '@vue/runtime-core';
export default{
data(){
return{
}
},
//组合式API 将同一个逻辑关注点相关代码收集起来
setup(){
//生命周期钩子函数中有一个参数 回调函数
onMounted(() => {
console.log('Component is mounted!')
})
onBeforeMount(()=>{
console.log('Component is onBeforeMount!')
})
}
}
</script>
<template>
</template>
<style>
</style>
运行效果
Setup 参数 props context
props
setup
函数中的props
是响应式的,当传入新的 prop 时,它将被更新。
简单使用
App.vue
<script>
import InputVue from './components/Input.vue'
export default{
data(){
return{
message:"父组件"
}
},
components:{
//加载组件
InputVue
}
}
</script>
<template>
<InputVue :msg='message'></InputVue>
</template>
<style>
</style>
Input.vue
<script>
export default{
data(){
return {
message:"子组件"
}
},
props:{
msg:{
type:String,
default:"hello"
}
},
setup(props){
console.log(props)
}
}
</script>
<template>
</template>
<style>
</style>
结果
toRefs
因为
props
是响应式的,你不能使用 ES6 解构,它会消除 prop 的响应性。
setup
函数中使用 toRefs 函数来完成
简单使用
App.vue
<script>
import InputVue from './components/Input.vue'
export default{
data(){
return{
message:"父组件"
}
},
components:{
//加载组件
InputVue
},
methods:{
changeMsg(){
this.message="parent component";
}
}
}
</script>
<template>
<InputVue :msg='message'></InputVue>
<button @click="changeMsg">改变message</button>
</template>
<style>
</style>
Input.vue
未使用 toRefs
<script>
import { onUpdated, toRefs } from '@vue/runtime-core';
export default{
data(){
return {
introduce:"子组件"
}
},
props:{
msg:{
type:String,
default:"hello"
}
},
setup(props){
console.log(props.msg);
const { msg } = props;
onUpdated(()=>{
console.log("进入onUpdated函数")
console.log("未使用toRef====>"+props.msg);
console.log("未使用toRef====>"+msg);
})
}
}
</script>
<template>
</template>
<style>
</style>
运行效果
使用 toRefs
<script>
import { onUpdated, toRefs } from '@vue/runtime-core';
export default{
data(){
return {
introduce:"子组件"
}
},
props:{
msg:{
type:String,
default:"hello"
}
},
setup(props){
console.log(props.msg);
const { message } =toRefs(props);
onUpdated(()=>{
console.log("进入onUpdated函数");
console.log("使用了toRef====>props.msg==>"+props.msg);
console.log("使用了toRef====>message.value==>"+message.value);
})
}
}
</script>
<template>
</template>
<style>
</style>
运行效果
Contex
context
是一个普通 JavaScript 对象,暴露了其它可能在setup
中有用的值
简单探索
Input.vue
<script>
export default{
data(){
return {
introduce:"子组件"
}
},
props:{
msg:{
type:String,
default:"hello"
}
},
setup(props,context){
// Attribute (非响应式对象,等同于 $attrs)
console.log(context.attrs);
console.log("=====================");
// 插槽 (非响应式对象,等同于 $slots)
console.log(context.slots);
console.log("=====================");
// 触发事件 (方法,等同于 $emit)
console.log(context.emit);
console.log("=====================");
// 暴露公共 property (函数);
console.log(context.expose);
}
}
</script>
<template>
</template>
<style>
</style>
App.vue
<script>
import InputVue from './components/Input.vue'
export default{
data(){
return{
message:"父组件"
}
},
components:{
//加载组件
InputVue
},
methods:{
changeMsg(){
this.message="parent component";
}
}
}
</script>
<template>
<InputVue :msg='message' id="input" class="shape"></InputVue>
<button @click="changeMsg">改变message</button>
</template>
<style>
</style>
运行结果
context.emit context.expose
简单使用
Input.vue
<script>
import { ref } from '@vue/reactivity'
import { h } from '@vue/runtime-core';
export default{
data(){
return {
introduce:"子组件"
}
},
props:{
msg:{
type:String,
default:"hello"
}
},
setup(props,context){
const counter=ref(30);
function sendParent(){
context.emit('toPare',counter.value);
};
//通过expose暴露
context.expose({
counter,
sendParent
});
//返回渲染函数 没有暴露数据
return ()=>h('div',counter.value);
}
}
</script>
<template>
<!-- 可在返回结果 直接暴露sendParent()使用 -->
<button @click="sendParent">按钮实现emit</button>
</template>
<style>
</style>
App.vue
<script>
import InputVue from './components/Input.vue'
export default{
data(){
return{
message:"父组件"
}
},
components:{
//加载组件
InputVue
},
mounted(){
console.log("查看暴露数据=====");
console.log(this.$refs.InputVue);
console.log("使用sendParent函数=====");
this.$refs.InputVue.sendParent();
},
methods:{
sendPare(value){
console.log("$emit 子传父 数据");
console.log(value);
}
}
}
</script>
<template>
<InputVue :msg='message' id="input" class="shape" ref="InputVue" @toPare='sendPare'></InputVue>
</template>
<style>
</style>
运行结果
Provide / Inject
provide
函数允许你通过两个参数定义 property:
- name (
<String>
类型)- value
inject
函数有两个参数:
- 要 inject 的 property 的 name
- 默认值 (可选)
简单使用
App.vue
<script>
import { ref } from '@vue/reactivity';
import NavigationBarVue from './components/NavigationBar.vue';
import { provide } from '@vue/runtime-core';
export default{
data(){
return{
message:"根组件"
}
},
components:{
//加载组件
NavigationBarVue
},
setup(){
const name=ref('张三');
//把整个对象都传进入 不要name.value 要不然就不会响应式了
provide('name',name);
function changeName(){
name.value='王五';
}
return {
changeName
};
}
}
</script>
<template>
<NavigationBarVue></NavigationBarVue>
<button @click="changeName">响应式改变名称</button>
</template>
<style>
</style>
NavigationBar.vue
<template>
<SearchVue></SearchVue>
</template>
<script>
import SearchVue from './Search.vue'
export default {
data(){
return{
message:"祖先组件",
}
},
components:{
//加载组件
SearchVue
}
}
</script>
<style>
</style>
Search.vue
<template>
<InputVue></InputVue>
</template>
<script>
import InputVue from './Input.vue'
export default {
data(){
return{
message:"父组件"
}
},
components:{
//加载组件
InputVue
}
}
</script>
<style>
</style>
Input.vue
<script>
import { inject } from '@vue/runtime-core';
export default{
data(){
return {
introduce:"子组件"
}
},
setup(){
//可跨级通信 案例 App.vue->NavigationBar.vue->Search.vue->Input.vue
const name=inject('name');
return {
name
}
}
}
</script>
<template>
<h2>{{name}}</h2>
</template>
<style>
</style>
运行结果
注意点: 传入整个的对象 不用.value 要不然就没有响应式了
点击按钮前
点击按钮后
单文件组件 script setup
为什么使用
官网:script setup
<script setup>
是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。相比于普通的<script>
语法,它具有更多优势:
- 更少的样板内容,更简洁的代码。
- 能够使用纯 TypeScript 声明 props 和抛出事件。
- 更好的运行时性能 (其模板会被编译成与其同一作用域的渲染函数,没有任何的中间代理)。
- 更好的 IDE 类型推断性能 (减少语言服务器从代码中抽离类型的工作)。
简单使用
App.vue
<script setup>
// 顶层的绑定会被暴露给模板
// 当使用 <script setup> 的时候,任何在 <script setup> 声明的顶层的绑定 (包括变量,函数声明,以及 import 引入的内容) 都能在模板中直接使用
import { ref } from '@vue/reactivity';
import NavigationBarVue from './components/NavigationBar.vue';
import { provide } from '@vue/runtime-core';
//引入组件不需要注册
import InputVue from './components/Input.vue';
//定义变量 在模板中不需要暴露 直接使用
const number=20;
const name=ref('你好呀');
function changeName(){
name.value="hello";
}
</script>
<template>
<InputVue></InputVue>
<h2>{{number}}</h2>
<br>
<h2>{{name}}</h2>
<button @click="changeName">响应式改变名称</button>
</template>
<style>
</style>
Input.vue
<script>
import { inject } from '@vue/runtime-core';
export default{
data(){
return {
introduce:"子组件"
}
}
}
</script>
<template>
<h2>{{introduce}}</h2>
</template>
<style>
</style>
运行结果
点击按钮前
点击按钮后