开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天,点击查看活动详情
space 组件用的很少,看过源码之后,发现很多功能点都有但结构不复杂,适合作为入门学习的组件,所以选择 space 组件作为第二个学习的组件进行分析。当然,由于篇幅有限,个人能力也有不足之处,所以只会截取部分代码进行分析。源码可在 GitHub 中查看,详细的文档可在 ant-design-vue 官网查看。
第一部分 主体结构
- 整体结构分析
const Space = defineComponent({ // 进行类型推导的辅助函数
name: 'ASpace', // 组件名称
props: spaceProps(),
slots: ['split'], // 插槽
setup(props, { slots }) { // composition API 的入口
...
},
});
export default withInstall(Space); // 导出可以使用的组件
可以看出,主体结构中,比较复杂的就是 props 和 withInstall,下面会详细分析这两部分。
- 传入的 props
export const spaceProps = () => ({
prefixCls: String,
size: {
type: [String, Number, Array] as PropType<SpaceSize | [SpaceSize, SpaceSize]>,
},
direction: PropTypes.oneOf(tuple('horizontal', 'vertical')).def('horizontal'),
align: PropTypes.oneOf(tuple('start', 'end', 'center', 'baseline')),
wrap: { type: Boolean, default: undefined },
});
spaceProps 包含 prefixCls,size,direction,align,wrap 五个 props。
- 作为插件来使用
export const withInstall = <T>(comp: T) => {
const c = comp as any;
c.install = function (app: App) {
app.component(c.displayName || c.name, comp);
};
return comp as T & Plugin;
};
withInstall 方法将插件的定义方式封装成一个函数,这样方便代码的复用
第二部分 setup 函数主体
- 主要的函数
const { prefixCls, space, direction: directionConfig } = useConfigInject('space', props); // 使用注入的配置
const supportFlexGap = useFlexGapSupport(); // 判断是否支持 flex 的 row-gap
const size = computed(() => props.size ?? space.value?.size ?? 'small'); // 获取到 size
const horizontalSize = ref<number>();
const verticalSize = ref<number>();
watch(
size,
() => {
[horizontalSize.value, verticalSize.value] = (
(Array.isArray(size.value) ? size.value : [size.value, size.value]) as [
SpaceSize,
SpaceSize,
]
).map(item => getNumberSize(item)); // 根据 size 字符串获取到对应的数值,并保存到 horizontalSize 和 verticalSize
},
{ immediate: true },
);
useConfigInject 函数在前面的文章已经分析过,这里不再赘述,可以看 ant design vue 源码解析之 button
useFlexGapSupport 函数是通过 detectFlexGapSupported 函数来做判断的,该函数首先判断 canUseDocElement 是否为 true,接着创建一个 div,使用了 display:flex;flex-direction:column;row-gap;
样式,里面再包含两个 div,最后通过 scrollHeight 是否为1来得出 flexGapSupported 的结果。
由于 size 包含多种结果,所以需要分多种情况进行处理。如果是数组直接进行处理,不是数组拼凑成包含两个元素的数组。接着遍历里面的值,对于 small, middle, large 都转化成数值,其他数值的情况直接保留。最后保存到 horizontalSize 和 verticalSize 中。
第三部分 渲染函数分析
- cn 计算属性分析
const mergedAlign = computed(() =>
props.align === undefined && props.direction === 'horizontal' ? 'center' : props.align,
);
const cn = computed(() => {
return classNames(prefixCls.value, `${prefixCls.value}-${props.direction}`, {
// classNames 把多种类型的类名合并
[`${prefixCls.value}-rtl`]: directionConfig.value === 'rtl',
[`${prefixCls.value}-align-${mergedAlign.value}`]: mergedAlign.value,
});
});
classNames 函数可以把字符串,数组和对象形式的类名进行整合。另外包含一个 mergedAlign 计算属性,当 props.align 未定义且 props.direction 是 horizontal 的时候返回 center,否则返回 props.align
- style 计算属性分析
const style = computed(() => {
const gapStyle: CSSProperties = {};
if (supportFlexGap.value) {
gapStyle.columnGap = `${horizontalSize.value}px`;
gapStyle.rowGap = `${verticalSize.value}px`;
}
return {
...gapStyle,
...(props.wrap && { flexWrap: 'wrap', marginBottom: `${-verticalSize.value}px` }),
} as CSSProperties;
});
style 默认包含的 css 属性有 flexWrap,marginBottom。如果 supportFlexGap.value 为 true,增加 css 属性 columnGap 和 rowGap。
- 渲染函数分析
<div class={cn.value} style={style.value}>
{items.map((child, index) => {
let itemStyle: CSSProperties = {};
if (!supportFlexGap.value) {
if (direction === 'vertical') {
if (index < latestIndex) {
itemStyle = { marginBottom: `${horizontalSizeVal / (split ? 2 : 1)}px` };
}
} else {
itemStyle = {
...(index < latestIndex && {
[marginDirection.value]: `${horizontalSizeVal / (split ? 2 : 1)}px`,
}),
...(wrap && { paddingBottom: `${verticalSize.value}px` }),
};
}
}
return (
<>
<div class={itemClassName} style={itemStyle}>
{child}
</div>
{index < latestIndex && split && (
<span class={`${itemClassName}-split`} style={itemStyle}>
{split}
</span>
)}
</>
);
})}
</div>;
space 组件有一个 split 插槽,可以对包含的多个节点中间插入内容,但是官方的文档中并没有提及这个插槽。
遍历默认插槽中的内容后,考虑到 supportFlexGap.value 的值可能为 false,还额外补充了 padding 或者 margin 来增加间距。
第四部分 小结
space 组件整体结构简单但组件的基本要素都具备了,适合用来入门学习。另外,withInstall 方法用来统一导出组件,split 插槽可以在间距里面插入内容,这两点是意外收获,一个是较好的复用方法,另一个是隐藏的插槽。