setup 方法
为了开始使用组合式 API
,我们首先需要一个可以实际使用它的地方。在 Vue 3
的组件中,我们将此位置称为 setup
方法,如示例代码 4-2-1 所示。
<div id="app">
<component-b user="John" />
</div>
const componentB = {
props: {
user: {
type: String,
required: true
}
},
template:'<div></div>',
setup(props, context) {
console.log(props.user) // 打印'John'
return {} // 这里返回的任何内容都可以用于组件的其余部分
}
}
Vue.createApp({
components: {
'component-b': componentB
}
}).mount("#app")
setup 方法的参数
setup
方法接收两个参数,一个参数是 props
,它和之前讲解的组件通信中的 props
一样,可以接收到父组件传递的数据,同样,如果 props
是一个动态值,那么它就是 响应式的,会随着父组件的改变而更新。
请注意如果你解构了 |
但是,因为 props
是响应式的,用户不能使用 ES 6 解构,它会消除 props
的响应性。如果需要解构 props
,可以在 setup
方法中使用 toRefs
函数来完成此操作,代码如下:
setup(props, context) {
// 将 `props` 转为一个其中全是 ref 的对象,然后解构
const { user } = Vue.toRefs(props)
console.log(user.value) // 打印'John'
}
注意,如果采用 npm
来管理项目,可以采用如下 import
方式引入 toRefs
,包括后续的组合式 API 相关的方法:
import { toRefs } from 'vue'
如果 user
是可选的 props
,则传入的 props
中可能没有 user
。在这种情况下,需要使用 toRef
替代它,代码如下:
setup(props,context) {
// 将 `props` 的单个属性转为一个 ref
const { user } = Vue.toRef(props,'user')
console.log(user.value) // 打印'John'
}
setup
方法的另一个参数是 context
对象,context
是一个普通的 JavaScript 对象,它暴露组件的三个属性,分别是 attrs
、slots
、emit
和 expose
,并且由于是普通的 JavaScript 对象,因此使用 ES 6 解构,如示例代码 4-2-2 所示。
<div id="app">
<component-b attrone="one" @emitcallback="emitcallback">
<template v-slot:slotone>
<span>slot</span>
</template>
</component-b>
</div>
<script type="text/javascript">
const componentB = {
template: '<div></div>',
setup(props, { attrs, slots, emit, expose }) {
// Attribute(非响应式对象)
console.log(attrs) // 打印 {attrone: 'one'} 相当于 this.$attrs
// 插槽 (非响应式对象)
console.log(slots.slotone) // 打印 { slotone: function() {} },相当于 this.$slots
// 触发事件 (方法)
console.log(emit) // 可调用 emit('emitcallback') 相当于 this.$emit
// 暴露公共属性(函数)
console.log(expose)
},
}
const vm = Vue.createApp({
components: {
'component-b': componentB
},
methods: {
emitcallback() {
console.log('emitcallback')
}
}
}).mount("#app")
</script>
其中,attrs
对象是父组件传递给子组件且不在 props
中定义的静态数据,它是 非响应式的,相当于在没有使用 setup
方法时调用的 this.$attrs
效果。
slots
对象主要是父组件传递的插槽内容,注意 v-slot:slotone
需要配置插槽名字,这样 slots
才能接收到,它是非响应式的,相当于在没有使用 setup
方法时调用的 this.$slots
效果。
emit
对象主要用来和父组件通信,相当于在没有使用 setup
方法时调用的 this.$emit
效果。
expose
函数用于显式地限制该组件暴露出的属性,当父组件通过模板引用访问该组件的实例时,将仅能访问 expose
函数暴露出的内容:
export default {
setup(props, { expose }) {
// 让组件实例处于 “关闭状态”
// 即不向父组件暴露任何东西
expose()
const publicCount = ref(0)
const privateCount = ref(0)
// 有选择地暴露局部状态
expose({ count: publicCount })
}
}
该上下文对象是非响应式的,可以安全地解构。 |
setup 方法结合模版使用
如果 setup
方法返回一个对象,那么该对象的属性以及传递给 setup
的 props
参数中的属性都可以在模板中访问 ,如示例代码 4-2-3 所示。
<div id="app">
<component-b user="John" />
</div>
<script type="text/javascript">
const componentB = {
props: {
user: {
type: String,
required: true
}
},
template: '<div>{{user}} {{person.name}}</div>',
setup(props) {
const person = Vue.reactive({name: 'Son'})
// 暴露给 template
return {
person
}
}
}
const vm = Vue.createApp({
components: {
'component-b': componentB
}
}).mount("#app")
</script>
注意, |
setup 方法的执行时机和 getCurrentInstance 方法
setup
方法在组件的 beforeCreate
之前执行,此时由于组件还没有实例化,是无法像配置式 API 一样直接使用 this.xx
访问当前实例的上下文对象的,例如 data
、computed
和 methods
都没法访问,因此 setup
在和其他配置式 API 一起使用时可能会导致混淆,需要格外注意。
但是,Vue
还是在组合式 API 中提供了 getCurrentInstance
方法来访问组件实例的上下文对象,如示例代码 4-2-4 所示。
Vue.createApp({
setup() {
Vue.onMounted(()=>{
const internalInstance = Vue.getCurrentInstance()
internalInstance.ctx.add()// 打印'methods add'
})
},
methods:{
add(){
console.log('methods add')
}
}
}).mount("#app")
需要注意的是,不要把 getCurrentInstance
当作在配置式 API 中的 this
的替代方案来随意使用,另外 getCurrentInstance
方法只能在 setup
或生命周期钩子中调用,并且不建议在业务逻辑中使用该方法,可以在开发一些第三方库时使用。
与渲染函数一起使用
setup
也可以返回一个渲染函数,此时在渲染函数中可以直接使用在同一作用域下声明的响应式状态:
import { h, ref } from 'vue'
export default {
setup() {
const count = ref(0)
return () => h('div', count.value)
}
}
返回一个渲染函数将会阻止我们返回其他东西。对于组件内部来说,这样没有问题,但如果我们想通过模板引用将这个组件的方法暴露给父组件,那就有问题了。
我们可以通过调用 expose()
解决这个问题:
import { h, ref } from 'vue'
export default {
setup(props, { expose }) {
const count = ref(0)
const increment = () => ++count.value
// 将这个组件的方法暴露给父组件
expose({
increment
})
return () => h('div', count.value)
}
}