setup()
函数是组合式 API 的入口函数,所有的组合式 API 都需要放到setup()
函数内部执行。
export default{
setup(props, context){
// do...
}
}
setup()
函数是在beforCreate()
和created()
之前执行的,所以setup()
内部不能调用beforCreate()
和created()
这两个生命周期函数,而是使用setup()
来代替。
Vue3 没有完全去除选项 API,而是做了向下兼容,新增了组合式 API,开发者完全可以自由的选择任意一种或者混合性的编写开发项目。
export default{
setup(props, { attrs, slots, emit, expose }) {
console.log(props);
return {
msg: "Hello setup!"
}
}
}
setup()
函数的第一个参数是props
,其实就是对选项 API 中props
的引用。setup()
函数的第二个参数是context
,表示上下文的意思,该对象内部有attrs
、slots
、emit
和expose
这 4 个对象。
在**setup()**
函数中返回的对象会暴露给模板和组件实例。其他的选项也可以通过组件实例来获取**setup()**
暴露的属性。
在setup()
函数内部,是无法像选项 API 那样获取this
对象的,当访问this
的时候会得到undefinde
,你可以在选项式 API 中访问setup()
暴露的值,但反过来则不行。
:::info
为什么无法访问this
对象呢?
1、因为setup()
函数是在组件创建之前执行的,执行的时候还没有组件实例,所以也就没有this
,所以 Vue3 会把一些对象封装作为setup()
函数的参数。
2、使用组合式 API 编程的时候,一些函数都是通过 ESModule 导入的而不是挂载到实例上的,所以也就没有this
的必要了。
:::
props
<template>
<VSetup
title="This is title."
author="Xiechen"
:content="content" />
</template>
<script>
import VSetup from "./components/VSetup.vue";
export default {
components:{
VSetup
},
data() {
return {
content: "This is content."
};
}
}
</script>
<template>
<div class="">
<h1>{{ title }}</h1>
<p>{{ author }}</p>
<p>{{ content }}</p>
</div>
</template>
<script>
export default{
pprops: {
title: String,
author: String,
content: String
},
setup(props, context){
console.log(props);
}
}
</script>
你可以完全的把选项 API 和组合 API 混合使用。
props
是一个响应式的对象,你不能把该对象的属性进行解构使用,这会丢失数据的响应式。
<script>
export default{
mounted() {
setTimeout(() => {
this.content = "This is my content.";
}, 1000);
}
}
</script>
<template>
<div class="">
<h1>{{ title }}</h1>
<p>{{ author }}</p>
<p>{{ content }}</p>
<p>{{ myContent }}</p>
</div>
</template>
<script>
// 把 computed 导入进来
import { computed } from "vue"
export default{
pprops: {
title: String,
author: String,
content: String
},
setup(props, context){
const { title, author, content } = props;
const myContent = computed(() => "Content:" + content);
// setup() 中的数据必须 return 出去,模版才能进行使用
return {
myContent
}
}
}
</script>
可以看到,模版中的content
发生了变化,而解构后的myContent
却没有变。
如果你非要进行解构props
对象中的属性,可以使用toRefs
方法进行解构:
<script>
import { computed, toRefs } from "vue"
export default{
pprops: {
title: String,
author: String,
content: String
},
setup(props, context){
const { title, author, content } = toRefs(props);
console.log(title, author, content);
// 因为 content 被包装为 ref 对象,所以需要使用 .value 来获取值
const myContent = computed(() => "Content:" + content.value);
return {
myContent
};
}
}
</script>
这样得到的属性就全部都是响应式的了。
同样的,如果你只想把一个属性进行解构出来可以使用toRef()
方法来这么操作:
<script>
import { computed, toRefs, toRef } from "vue"
export default{
pprops: {
title: String,
author: String,
content: String
},
setup(props, context){
const content = toRef(props, "content");
const myContent = computed(() => "Content:" + content.value);
return {
myContent
};
}
}
</script>
context
context
对象里面包含了attrs
、emit
、slots
、expose
这 4 个对象。
其中,attrs
对应着选项 API 中的this.$attrs
,slots
对应着选项 API 中的this.$slots
,emit
对应着选项 API 中的this.$emit
。而expose
是 Vue3 新增的一个方法,这个本篇的后面再说。
<script>
export default{
pprops: {
title: String,
author: String,
content: String
},
setup(props, { attrs, slots, emit, expose }) {
console.log(attrs);
console.log(slots);
console.log(emit);
console.log(expose);
}
}
</script>
其中attrs
和slots
是对象,emit
和expose
是方法。attrs
和slots
都是有状态的对象,它们总是会随着组件自身的更新而更新。这意味着你应当避免解构它们,并始终通过attrs.x
或slots.x
的形式使用其中的属性。
此外还需注意,和props
不同,attrs
和slots
的属性都不是响应式的。如果你想要基于attrs
或slots
的改变来执行副作用,那么你应该在onBeforeUpdate
生命周期钩子中编写相关逻辑。
expose
expose()
是干什么的?expose()
函数用于显式地限制该组件暴露出的属性,当父组件通过模板引用访问该组件的实例时,将仅能访问expose
函数暴露出的内容:
export default {
setup(props, { expose }) {
// 让组件实例处于 “关闭状态”
// 即不向父组件暴露任何东西
expose()
const publicCount = ref(0)
const privateCount = ref(0)
// 有选择地暴露局部状态
expose({ count: publicCount })
}
}
setup()
函数也可以返回一个渲染函数,此时在渲染函数中可以直接使用当前作用域下的响应式状态,但是当setup()
函数返回一个渲染函数将会阻止我们返回其他东西。这对于组件内部来说,这样没有问题,但如果我们想通过模板引用将这个组件的方法暴露给父组件,那就有问题了。
<!-- 父组件通过插槽的形式调用 -->
<template>
<VSetup ref="VSetupRef">
<template #default>This is title.</template>
<template #author>Xiechen</template>
<template #content>{{ content }}</template>
</VSetup>
</template>
<script>
import { ref, onMounted } from "vue";
import VSetup from "./components/VSetup.vue";
export default {
components: {
VSetup
},
data() {
return {
content: "This is content."
};
},
setup() {
const VSetupRef = ref(null);
onMounted(() => {
console.log(VSetupRef.value);
});
return {
VSetupRef
};
}
};
</script>
<script>
import { h } from "vue";
export default {
setup(props, context) {
const testStr = "This is test str."
return () =>
h("div", null, [
h("h1", null, context.slots.default()),
h("p", null, context.slots.author()),
h("p", null, context.slots.content()),
h("p", null, testStr)
]);
}
}
</script>
就目前的结构来说,父组件是拿不到子组件中的testStr
属性的。
我们可以通过调用expose()
解决这个问题:
<script>
import { h } from "vue";
export default {
setup(props, context) {
const testStr = "This is test str."
context.expose({
testStr
})
return () =>
h("div", null, [
h("h1", null, context.slots.default()),
h("p", null, context.slots.author()),
h("p", null, context.slots.content()),
h("p", null, testStr)
]);
}
}
</script>
然后父组件就可以拿到这个属性了:
getCurrentInstance()
在setup()
函数中,我们可以通过导入getCurrentInstance()
方法来获取当前组件的实例对象:
<script>
import { getCurrentInstance } from "vue";
export default {
setup(props, context) {
console.log(getCurrentInstance());
}
}
</script>
:::warning
⚠️ 注意
但是 Vue3 不建议在应用代码中进行使用,也不建议使用getCurrentInstance()
来代替this
对象!!!
另外,getCurrentInstance()
函数只能在setup()
内部执行。
:::