无声同学是我们 DevUI 开源组件库的田主,从2021年9月份进入 DevUI 开源组织到现在,一直在持续贡献,为 Skeleton / Table / Avatar 等多个组件贡献过代码,累计提交次数达到 53 次。
感谢无声同学的辛勤付出,在参与 DevUI 开源项目的过程中,不仅积极调研业界竞品,积极主动地确认组件开发的细节,而且沉淀了多篇技术文章,以下就是他在开发 Skeleton 骨架屏组件时沉淀的其中一篇文章。
除了无声同学,还有很多小伙伴参与到 DevUI 开源项目中来,并且都有自己的收获,也欢迎大家参与我们的 Vue DevUI / React DevUI / Ng DevUI 等多个开源项目,一起学习和成长!(添加 DevUI 小助手微信:devui-official
)
- TinsFox:开源贵在坚持、贵在追求完美。聊聊开源的那些事
- duqingyu:与其插肩而过,不如拥抱开源。我与开源不再擦肩而过!【DevUI】
- CatsAndMice:让开源成为一种习惯,让提issue & PR成为一种习惯。开源初衷
- Bob:不是等你有能力再去做事,而是在做事中获得能力。我与DevUI的开源故事
- AlanLee:努力提升自己,比仰望别人更有意义。参与Vue DevUI开源项目的小故事
- Wailen:伸手摘星,即使一无所获,也不至于满手污泥。参与华为Devui开源组件库的感受
- MICD:很多时候不是因为你看到希望了才要努力,而是你努力了才会有相应的回报。我与vue-Devui开源组件库的故事
- iel 手把手带你开发一个脚手架(上) / 我为 Devui 开发的脚手架
- JS老狗 我与DevUI专栏 / DevUI中VUE的TSX函数式组件实践 / 再聊Vue的TSX函数式组件
以下是正文:
前言
之前总结了给 DevUI 开发骨架屏(Skeleton)的一些心得:
Kagol 老师看到之后提出增加拼接模式。之所以到现在才更新,一方面是因为最近换了个项目组,另一方面是思考如何设计 API 让两种模式风格统一(才不是因为 lol 手游和云顶 S6 的关系)。
两种颗粒度
粗粒度模式
默认模式即粗粒度模式,也可以看作是细粒度的拼接模式。这个模式下,骨架屏大致包含头像、标题、段落,如下图所示。
细粒度模式
即指完整的骨架屏被拆成细粒度的骨架屏元素,从图形的角度可以分为圆形和矩形,从功能的角度可以分为占位头像、占位图像、占位标题、占位内容、占位按钮。
比较
相比默认模式,细粒度的骨架屏元素给使用这个组件的开发者提供了更大的灵活和定制能力。市面上,Element-ui 和 Vant-ui 采用默认模式,抖音的 SemiDesign 采用拼接模式,Antd 则兼具二者。似乎 React 的组件库会更倾向于细粒度?
API设计
默认模式下,由于多个元素被包裹在根节点下,不方便直接设置样式,所以提供了许多样式 API 。而在拼接模式下,由于本身就是多根节点,类似宽高等样式可以直接通过 style 去控制,再设计额外的 API 就显得多余。
非Prop的Attribute
当我尝试直接通过 style 去控制组件的样式时,控制台报了警告,而之前在默认模式下没有这个问题,我又试了试 ICON 组件发现了同样的问题。
起初我还以为这是项目没有做相关配置的问题,后来在行言同学的指导下发现了问题。
Vue 3 文档的表述是:一个非 prop 的 attribute 是指传向一个组件,但是该组件并没有相应 props 或 emits 定义的 attribute。常见的示例包括 class
、style
和 id
attribute。可以通过 $attrs
property 访问那些 attribute。
在 JSX 中用ctx.attrs
来传入,具体如下:
<div
class={`devui-skeleton__shape__${props.shape} ${renderAnimate(props.animate)}`}
{...ctx.attrs}
/>
实现
第一版 PR 尽管在功能上实现了,但在 code review 的时候给打回了,原因在于两种模式放在同一个文件下导致内容太大,功能比较杂乱。
Kagol 眼中的理想的模式应该是:
- 将骨架屏划分成
d-skeleton
和d-skeleton-item
。 d-skeleton
组件其实只是将d-skeleton-item
拼接起来,我们可以内置一些拼接模式,这部分和目前实现的API可以保持一致。
因此我将拼接模式的代码拆分到 item 文件夹下,再在 index 中通过 app.component
注册组件。最终单个 TSX 文件长度控制在了 150 行以内。
import type { App } from 'vue'
import Skeleton from './src/skeleton'
import SkeletonItem from './src/item/item'
export { Skeleton, SkeletonItem }
export default {
title: 'Skeleton 骨架屏',
category: '数据展示',
status: '100%',
install(app: App): void {
app.component(Skeleton.name, Skeleton)
app.component(SkeletonItem.name, SkeletonItem)
}
}
参考
- 非 Prop 的 Attribute: v3.cn.vuejs.org/guide/compo…
- Vue3 JSX 使用指南: mp.weixin.qq.com/s/SNC5pq89N…
闲谈
秀下 DevUI 发的抱枕(掘金的徽章啥时候才能到呢)
原文链接:mp.weixin.qq.com/s/DkqUyTZ-U…
作者:无声(欢迎关注无声同学的公众号前端手记
)