Composition API
认识Composition API
前几篇文章中主要用Options API进行编写代码,但是有一些弊端
- Options API弊端
- 在 Options API中,有data、watch、computed、methods以及生命周期函数各种各样的选项
- 而一个变量,为了实现某个功能,会分散在各个选项中,不利于代码的维护
- 此时 Composition API 就出现了
- 使用 setup函数可以将大部分选项代替,data、watch、computed、methods以及生命周期函数
ssetup函数的参数
-
setup函数主要有两个参数
- props
- context
-
props是一个对象,包含着父组件传递过来的属性
-
context主要包含三个属性
- attrs:所有的非prop的attribute
- **slots:**父组件传递过来的插槽(在以渲染函数返回时会有作用)
- **emit:**当我们组件内部需要发出事件时,会用到emit(在setup函数中,不能访问this)
-
简单实现
vue
<template>
<div>
<span>{{ count }}</span>
<button @click="add">+1</button>
<button @click="deAdd">-1</button>
</div>
</template>
<script>
//引入ref函数
import { ref } from "vue";
export default {
setup() {
//定义变量,此时count并不是响应式数据,需要用ref进行包裹
let count = ref(0);
//定义函数
//要对count进行修改,需要通过.value的方式进行修改
let add = () => count.value++;
let deAdd = () => count.value--;
//将变量以及函数返回出去,template中才可以使用
return {
count,
add,
deAdd,
};
},
};
</script>
- 同时我们可以将内部的逻辑代码,封装单独的一个文件,而后进行引入
vue
<script>
//引入函数
import counter from "./utils/counter";
export default {
setup() {
let { count, add, deAdd } = counter();
return {
count,
add,
deAdd,
};
},
};
</script>
-------还可以做以下优化
<script>
//引入函数
import counter from "./utils/counter";
export default {
setup() {
return {
...counter()
};
},
};
</script>
setup定义数据
- 定义普通函数
js
setup(){
//可以直接使用,但不是响应式
let message = "hello"
return{
message
}
}
- 定义响应式数据(reactive )
- 定义复杂数据:对象/数组等,不能传入普通类型的,比如字符串等
- 在 option api中data中定义的数据实际上就是交给了reactive函数
js
import {reactive} from "vue"
setup(){
//此时就是响应式了
let obj = reactive({
a:100,
b:200
})
//对obj进行修改
obj.a = 300
return{
obj
}
}
- 定义响应式数据(ref ):返回的是ref对象,需要使用.value进行获取值
- 定义简单类型的响应式数据
- 也可以定义复杂类型的数据
- 但是在template中,会自动解包,即自动获取 变量.value的值,直接使用变量即可
js
<template>
{{count}}
</template>
import {ref} from "vue"
setup(){
//定义简单数据,也可以定义复杂数据
let count = ref(0)
//修改变量
count.value = 100
return {
count
}
}
ref定义数据和reactive定义数据的开发
- 在实际开发中,使用ref 的频次比较多
- ref可以定义复杂数据
- reactive 应用场景
- 应用于本地数据
const obj = reactive({username:"123"})
,不是从数据库中得来的 - 多个数据之间是有关系/联系的
const user = reactive({username:"admin",password:"123456"})
- 应用于本地数据
- ref 应用场景:其余的场景基本都用ref
- 定义本地的简单数据
- 定义从网络获取的数据
js
const music = ref([])
onMounted(()=>{
const serverMusic = ["野狼","小苹果"]
music.value = serverMusic
})
单向数据流的规范和做法
- 父组件传递给子组件一个对象/数组,此时,子组件能否对其进行修改?
- 在代码层面,子组件是可以进行修改的 ---但是,不符合规范
- 因为父组件引用的子组件很多的情况下 ,若子组件擅自修改了对象中的数据,那么就不清楚到底是谁对数据进行了修改
- 正确的做法
- 父组件将数据传递给了子组件
- 子组件抛出事件,以及要修改的值
- 父组件使用 @子组件事件,来将数据进行修改
readonly函数
创建出来的数据也是响应式数据,但是不能对数据进行修改,但是我们可以对元数据进行修改
- 其返回的也是一个Proxy对象,只是setter方法受到了限制
js
setup(){
let obj = ref({
a:100
})
//此时readonlyObj不能进行修改,可以将其传给子组件
let readonlyObj = readonly(obj)
//若我们向修改其内容,可以对obj进行修改
}
Reactive判断的API
-
isProxy
- 检查对象 是否由reactive或者readonly创建的Proxy
-
isReactive
- 检查对象是否由 reactive创建的响应式代理
-
isReadonly
- 检查对象 是否由readonly创建的只读代理
-
toRaw
- 返回proxy代理对象的原始数据
-
shallowReactive
- 创建一个响应式代理,但是只响应浅层次的数据
-
shallowReadonly
- 创建一个响应式代理,但是不执行嵌套对象的深度只读转换
toRef和toRefs
- 现在有如下代码
- 我们在使用user的时候,想直接通过name age height的方式直接展示
- 不是通过 user.name user.age user.height的方式展示
vue
<template>
<div>
<div>{{ user.name }}-{{ user.age }}-{{ user.height }}</div>
<button @click=""></button>
</div>
</template>
<script>
//引入函数
import { reactive, ref, toRef, toRefs } from "vue";
export default {
setup() {
let user = reactive({
name: "zhangcheng",
age: 18,
height: 1.88,
});
return {
user,
};
},
};
</script>
<style scoped></style>
- 为了实现上述要求可以将user进行解构
- 之后将解构的数据返回出去
js
const {name,age,height} = user
- 但是解构出来的变量,就是普通的变量,不再是响应式数据了
- 因此我们可以通过 toRef和toRefs方法进行操作
js
let user = reactive({
name: "zhangcheng",
age: 18,
height: 1.88,
});
//toRefs对多个值进行ref转化
const { name, age } = toRefs(user);
// toRef对某个属性进行ref转化
const height = toRef(user, "height");
- 这种做法实际上是将 name.value与user.name做了某种联系,使其中一方修改,另外一方也会进行修改
ref定义数据其他的API
unref
- unref实际上是 语法糖
- 其内部会先判断传进来的参数是否是ref对象,是的化就返回 ref.value,不是的化就直接返回值本身
js
const a = ref(2)
const b = unref(a)//内部实际上是 b = isRef(a)?a.value:a
isRef
- 判断值 是否是一个ref对象
setup函数中不能使用this
- 在官方文档中,有对setup函数中不能使用this进行过提示
- 大概意思如下
- this并没有指向当前组件的实例
- 在 setup被调用之前,data,computed、methods等都没有被解析
computed函数使用
当某些数据需要进行某些逻辑转化,再显示,就会用到计算属性 computed
- 在里面传入一个回调函数,默认调用的是其中的getter方法
- 此时 computedStr变量就是计算属性
- 且这个计算属性,是只读的
js
import {computed} from "vue"
setup(){
const computedStr = computed(()=>{
return "a+b"
})
return {
computedStr
}
}
- 如果想要调用computed中的getter方法 ,需要在computed函数 中传入对象
- computed函数返回值实际上是ref对象
- 因此想要修改的话,需要使用 .value进行修改
js
import {computed} from "vue"
//可以调用setter方法
const computedObjStr = computed({
set: function (newValue) {
const temp = newValue.split(" ");
str.first = temp[0];
str.last = temp[1];
},
get: function () {
return str.first + str.last;
},
});
//若想修改computedObjStr的话,需要computedObjStr.value="li si"
ref引入元素
此ref不是定义数据的ref
在vue中,我们可以通过ref this.$refs.ref进行获取元素,那么在setup函数中怎么使用
- 直接在setup函数内部打印 ref引用的元素是不会获取到的
- 因此,需要在onMounted生命周期中查看
html
<template>
<div>
<div ref="table">123</div>
<div ref="divEl">456</div>
</div>
</template>
<script>
//引入函数
import { onMounted, ref, reactive, computed } from "vue";
export default {
setup() {
const refTable = ref();
const divEl = ref();
onMounted(() => {
//需要使用.value获取值
//对于组件使用也是如此
console.log(refTable.value);
console.log(divEl.value);
});
return {
//两种返回方式,一种是对象的增强写法
table: refTable,
divEl,
};
},
};
</script>
<style scoped></style>
生命周期钩子
在setup中使用生命周期的方式 onX
- 需要进行导入 生命周期的函数调用
- 之后再 其内部传入函数,才是生命周期的函数
- 在 vue的官网中,有这样的提示,setup就是围绕 beforeCreate和created进行的,因此之前在该生命周期中做的事,可以直接写在setup中
选项式API | Hook inside setup |
---|---|
beforeCreate | 不需要 |
created | 不需要 |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
activated | onActivated |
deactivated | onDeactivated |
js
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onActivated,
onDeactivated,
} from "vue";
setup() {
onMounted(() => {
console.log("onMounted 执行");
});
return {};
},
provide和inject函数
- 在 option API中我们用到了provide和inject用于组件之间传递数据
- 那么 在 composition API中要怎么使用
- provide(key,value):key是传递参数的代表,value 是具体数据
- inject(key,default):key是父组件传递参数的代表,default是父组件没有传递数据的时候的默认值
js
import { provide, inject } from "vue";
//父组件
const obj = ref({ a: 100 })
provide("objStr", obj)
//子组件
inject("objStr",{a:0})
侦听数据的变化
在option API中使用的是watch中进行监听数据的变化,那么在composition中怎么进行监听
watch
- 首先导入 watch函数
- 基本使用
- 可以监听多个数据
- newValue, oldValue返回的都是proxy对象
js
import { reactive, ref, watch } from "vue";
export default {
setup() {
/**
* 参数
* 1.数据源
* 针对于ref包裹的需要监听.value
* 使用数组可以监听多个数据
* 2.执行的逻辑
* 3.配置项:配置deep以及immediate
* 默认就是深度监听
*/
//定义数据
let obj = ref({ a: 100, b: { c: 200 } });
let objRea = reactive({ username: "zhangcheng" });
//对数据进行监听
watch([objRea, obj.value], (newValue, oldValue) => {
console.log(newValue, oldValue);
},
{
immediate: true,
});
return { obj, objRea };
},
};
};
- 若 newValue, oldValue返回的是普通对象
- 数据源需要传递一个函数
- 且配置项中需要进行深度监听的配置,默认不启用深度监听
js
/**
* 参数
* 1.数据源
* 针对于ref包裹的需要监听.value
* 使用数组可以监听多个数据
* 2.执行的逻辑
* 3.配置项:配置deep以及immediate
* 默认就是深度监听
*/
let obj = ref({ a: 100, b: { c: 200 } });
let objRea = reactive({ username: "zhangcheng", b: { c: 200 } });
watch(
() => ({ ...obj.value, ...objRea }),
(newValue, oldValue) => {
console.log(newValue, oldValue);
},
{
deep: true,
immediate: true,
}
);
watchEffect
相较于watch而言,watchEffect可以自动监听依赖变量的变化
- 传入warchEffect中的回调函数,在页面初次加载的时候会先执行一次
- 而后在依赖的数据发生变化的时候,也会执行回调函数
- 同时,达到某一个条件的时候,可以停止数据的监听
js
const stopWatch = watchEffect(() => {
//当obj.value发生变化的时候,会自动调用这个回调函数
console.log(obj.value);
//执行watchEffect的返回值,就可以停止对数据的监听
if (obj.value > 10) {
stopWatch();
}
});
script setup语法
本质就是setup函数的语法糖
- 就是在 script的标签中加入setup的attribute
- 这种写法有更好的运行效率,因为内部会将script与template模板放在同一个作用域中
- 代码会更加的简洁
- 有更好的提示
html
<script setup>
//实际上就是在setup中在编写代码
//不用写return
</script>
顶层的绑定会直接暴露给模板
- 在 script setup标签中直接写的内容,在template中可以直接使用
- 若是在函数中,定义一个变量,则不属于顶层
html
<template>
{{ message }}
</template>
<script setup>
//引入函数
import { ref } from "vue";
let message = ref(123);
</script>
<style scoped></style>
导入组件直接使用
- 导入的组件,无需注册, 直接在template中使用即可
html
<template>
<home-vue></home-vue>
</template>
<script setup>
//导入组件直接使用
import HomeVue from "./components/HomeVue";
</script>
<style scoped></style>
- 子组件通过 defineProps()接受父组件传进来的参数,defineEmits()发送事件
html
<template>
<button @click="messageClick">{{ message }}</button>
</template>
<script setup>
//defineEmits和defineProps都是内部绑定的,因此不用注册
//接受参数
defineProps({
message: {
type: String,
default: "默认信息",
},
});
//定义发射的事件
const messageEmit = defineEmits(["message-click"]);
function messageClick() {
messageEmit("message-click", "传递参数");
}
</script>
<style scoped></style>
defineExpose将子组件中的数据暴露
默认都是在
<script setup></script>标签中写的代码
- 子组件
- 定义变量,函数等,之后暴露出去
js
import { ref } from "vue";
function expsoeFun() {
console.log("暴露出去了");
}
const message = ref("我是变量");
//子组件中有一个函数想被父组件直接调用,需要用到 defineExpose将函数暴露,或者将变量暴露
defineExpose({ expsoeFun, message });
- 父组件
- 首先通过ref获取子组件,之后调用其暴露的内容即可
js
import { onMounted, ref } from "vue";
import HomeVue from "./components/HomeVue.vue";
//绑定实例
const homeVue = ref();
onMounted(() => {
//调用实例中的方法
homeVue.value.exposeFun();
//获取实例中的变量
console.log(homeVue.value.message);
});