持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第19天,点击查看活动详情
前言
上一篇我们讲了 setup 函数的用法,知道了 setup 函数中接收了两个参数,一个是 props,一个是 context。而 context 暴露了一些方法,attrs、slots、emit、expose。其中的attrs、slots、emit在vue2中都比较常用,那 expose 这个方法是用来干什么的呢?我们今天就来研究一下。
用法
当我在封装一个组件的时候,如果觉得暴露出去的属性和方法太多了,那么可以使用这个方法,expose 方法可以限制公共实例可以访问的属性。也就是说限制父组件访问这个组件的属性。
// 子组件
import { ref } from 'vue'
export default {
setup(props) {
const count = ref(0)
function increment() {
count.value++
}
return { increment, count }
}
}
// 父组件
<template>
<div @click="handleClick">
<h2>我是父组件!</h2>
<Child ref="child" />
</div>
</template>
<script>
import { ref } from 'vue';
import Child from './Child.vue'
export default defineComponent({
components: {
Child
},
setup() {
const child = ref(null)
const handleClick = () => {
child.value.increment();
console.log(child.value.count);
}
return {
child,
handleClick
}
}
});
</script>
我们定义了一个子组件和父组件,子组件中有一个属性 count 和方法increment。父组件中有一个方法 handleClick,这个方法中调用了子组件的 increment 方法和打印出 count 属性。
目前情况下我们调用 handleClick 是没问题的,count 会被增加并被返回回来。但如果现在在子组件中改一下代码。
// 子组件
import { ref } from 'vue'
export default {
setup(props, {expose}) {
const count = ref(0)
function increment() {
count.value++
}
expose({
increment
})
return { increment, count }
}
}
这时候在调用 handleClick 会发现 increment 还是调用成功了,但是 count 我们却无法再访问。这就是 expose 的作用。
源码
expose 的源码在 packages/runtime-core/src/component.ts 中
export function createSetupContext(
instance: ComponentInternalInstance
): SetupContext {
const expose: SetupContext['expose'] = exposed => {
if (__DEV__ && instance.exposed) {
warn(`expose() should be called only once per setup().`)
}
instance.exposed = exposed || {}
}
let attrs: Data
return {
get attrs() {
return attrs || (attrs = createAttrsProxy(instance))
},
slots: instance.slots,
emit: instance.emit,
expose
}
}
可以看到 expose 方法很简单,只做了一件事情,就是把我们传过去的需要暴露的对象直接赋值给 instance.exposed,那为什么一句话就能实现这个功能呢?
我们再来看下这个方法 getExposeProxy
export function getExposeProxy(instance: ComponentInternalInstance) {
if (instance.exposed) {
return (
instance.exposeProxy ||
(instance.exposeProxy = new Proxy(proxyRefs(markRaw(instance.exposed)), {
get(target, key: string) {
if (key in target) {
return target[key]
} else if (key in publicPropertiesMap) {
return publicPropertiesMap[key](instance)
}
}
}))
)
}
}
在这个方法中,首先判断 instance.exposed 是否存在,不存在的话就不进行返回,也就是说没有 instance.exposed 的话就能访问子组件的所有属性。
如果存在的话,设置代理,并在 getter 中判断,需要访问的 key 是否在 instance.exposed 或者 publicPropertiesMap 中,存在就返回。
在 publicPropertiesMap 中定义了一些公共方法。
export const publicPropertiesMap: PublicPropertiesMap =
extend(Object.create(null), {
$: i => i,
$el: i => i.vnode.el,
$data: i => i.data,
$props: i => (__DEV__ ? shallowReadonly(i.props) : i.props),
$attrs: i => (__DEV__ ? shallowReadonly(i.attrs) : i.attrs),
$slots: i => (__DEV__ ? shallowReadonly(i.slots) : i.slots),
$refs: i => (__DEV__ ? shallowReadonly(i.refs) : i.refs),
$parent: i => getPublicInstance(i.parent),
$root: i => getPublicInstance(i.root),
$emit: i => i.emit,
$options: i => (__FEATURE_OPTIONS_API__ ? resolveMergedOptions(i) : i.type),
$forceUpdate: i => () => queueJob(i.update),
$nextTick: i => nextTick.bind(i.proxy!),
$watch: i => (__FEATURE_OPTIONS_API__ ? instanceWatch.bind(i) : NOOP)
} as PublicPropertiesMap)
所以我们设置了 exposed 后,除了 exposed 里面的属性可以访问,也可以访问子组件的这些公共方法。