Trigger源码分析 – ant-design-vue系列
1 概述
源码地址: https://github.com/vueComponent/ant-design-vue/blob/main/components/vc-trigger/Trigger.tsx
在源码的实现中,Trigger组件主要有两个作用:
- 使用
Portal
组件,把Popup
组件传送到指定的dom
下,默认是body
。 - 为
target
节点绑定事件,控制事件的触发逻辑。
2 极简实现
为了实现以上功能,我们可以和源码一样,使用vue3
提供的Teleport
组件,来实现节点的传送;同时把所有事件进行透传即可。
在这里trigger
就是我们原先的target
节点,可以翻译成切换器。
setup(props, { slots }) {
const align: any = computed(() => {
const { placement } = props;
return placements[placement];
});
const getComponent = () => {
return (
<Popup
style={{ position: 'absolute' }}
target={() => triggerRef.value!}
align={align.value}
visible={props.visible}
>
{slots.popup?.()}
</Popup>
);
};
const triggerRef = ref<HTMLElement>();
return () => {
// 1 Popup 部分
const portal = <Portal>{getComponent()}</Portal>;
// 2 target部分
const trigger = (
<div style={{ display: 'inline-block' }} ref={triggerRef}>
{slots.default?.()}
</div>
);
return (
<>
{portal}
{trigger}
</>
);
};
}
3 源码分析
3.1 整体结构
这个组件比较特殊,使用了选项式
的写法。
export default defineComponent({
name: 'Trigger',
mixins: [BaseMixin],
inheritAttrs: false, // 用于控制组件的根元素是否应该继承父作用域中的属性(attribute)和事件监听器(listener)。
porps: {},
setup() {}, // 使用props提供的响应式变量,这里是 定位&portal 相关的;并且声明了一些初始值
data() (), // 处理visible变量,为this挂载所有事件,尝试让PopupRef变量指向Portal
watch: (), // 监听visible的变化
created() {}, // 依赖注入,提供vcTriggerContext和PortalContextKey上下文
deactivated() {}, // 组件失活时,关闭popup弹窗
mounted() {}, // 调用updatedCal(),这个函数的作用是在visible为true的时候,注册点击/滚动/失焦的相关事件,以便于在点击popup外部/页面滚动/窗口失焦的时候关闭弹窗;在visible为false时,移除事件监听。
updated() {}, // 组件属性更新后调用updatedCal(),重新注册。
beforeUnmount() {}, // 卸载前清除所有监听器
methods: {}, // 事件的执行、事件是否绑定、获取组件的方法等
render() {} // 渲染trigger和portal
})
3.2 render函数
从const child = children[0];
来看,代码默认使用第一个子节点,所以调用的时候最好只传入一个子节点。
render() {
const { $attrs } = this;
const children = filterEmpty(getSlot(this));
const { alignPoint } = this.$props;
const child = children[0];
this.childOriginEvents = getEvents(child);
const newChildProps: any = {
key: 'trigger',
};
/**
* 这里有各种事件,其他删除,以click为例
*/
if (this.isClickToHide() || this.isClickToShow()) {
newChildProps.onClick = this.onClick;
newChildProps.onMousedown = this.onMousedown;
newChildProps[supportsPassive ? 'onTouchstartPassive' : 'onTouchstart'] = this.onTouchstart;
} else {
newChildProps.onClick = this.createTwoChains('onClick');
newChildProps.onMousedown = this.createTwoChains('onMousedown');
newChildProps[supportsPassive ? 'onTouchstartPassive' : 'onTouchstart'] =
this.createTwoChains('onTouchstart');
}
/**
* 这个函数内部是vue3提供的cloneVNode实现的
*/
const trigger = cloneElement(child, { ...newChildProps, ref: 'triggerRef' }, true, true);
if (this.popPortal) {
return trigger;
} else {
const portal = (
<Portal
key="portal"
v-slots={{ default: this.getComponent }}
getContainer={this.getContainer}
didUpdate={this.handlePortalUpdate}
></Portal>
);
return (
<>
{portal}
{trigger}
</>
);
}
},