Skip to content

骨架屏优化——细粒度模式的实现

之前总结了给 DevUI 开发骨架屏(Skeleton)的一些心得,Kagol 老师看到之后提出增加细粒度模式。之所以到现在才更新,一方面是因为最近换了个项目组,另一方面是思考如何设计 API 让两种模式风格统一(才不是因为 lol 手游和云顶 S6 的关系)。

Kagol 拼接模式建议

两种模式

拼接模式

默认模式即粗粒度模式,也可以看作是细粒度的拼接模式。这个模式下,骨架屏大致包含头像、标题、段落,如下图所示。

常见骨架屏

细粒度模式

即指完整的骨架屏被拆成细粒度的骨架屏元素,从图形的角度可以分为圆形和矩形,从功能的角度可以分为占位头像、占位图像、占位标题、占位内容、占位按钮。

拼接模式

比较

相比默认模式,细粒度的骨架屏元素给使用这个组件的开发者提供了更大的灵活和定制能力。市面上,Element-ui 和 Vant-ui 采用默认模式,抖音的 SemiDesign 采用拼接模式,Antd 则兼具二者。似乎 React 的组件库会更倾向于细粒度?

API 设计

默认模式下,由于多个元素被包裹在根节点下,不方便直接设置样式,所以提供了许多样式 API 。而在拼接模式下,由于本身就是多根节点,类似宽高等样式可以直接通过 style 去控制,再设计额外的 API 就显得多余。

非 Prop 的 Attribute

当我尝试直接通过 style 去控制组件的样式时,控制台报了警告,而之前在默认模式下没有这个问题,我又试了试 ICON 组件发现了同样的问题。

Extraneous non-props attributes (class) were passed to component but could not be automatically inherited because component renders fragment or text root nodes.

起初我还以为这是项目没有做相关配置的问题,后来在行言同学的指导下发现了问题。

行言同学的建议

Vue 3 文档的表述是:一个非 prop 的 attribute 是指传向一个组件,但是该组件并没有相应 propsemits 定义的 attribute。常见的示例包括 classstyleid attribute。可以通过 $attrs property 访问那些 attribute。

在 JSX 中用ctx.attrs来传入,具体如下:

tsx
<div
  class={`devui-skeleton__shape__${props.shape} ${renderAnimate(
    props.animate
  )}`}
  {...ctx.attrs}
/>

实现

第一版 PR 尽管在功能上实现了,但在 code review 的时候给打回了,原因在于两种模式放在同一个文件下导致内容太大,功能比较杂乱。

Kagol 眼中的理想的模式应该是:

  1. 将骨架屏划分成d-skeletond-skeleton-item
  2. d-skeleton组件其实只是将d-skeleton-item拼接起来,我们可以内置一些拼接模式,这部分和目前实现的 API 可以保持一致。

因此我将拼接模式的代码拆分到 item 文件夹下,再在 index 中通过 app.component注册组件。最终单个 TSX 文件长度控制在了 150 行以内。

tsx
import type { App } from "vue";
import Skeleton from "./src/skeleton";
import SkeletonItem from "./src/item/item";

Skeleton.install = function (app: App): void {
  app.component(Skeleton.name, Skeleton);
  app.component(SkeletonItem.name, SkeletonItem);
};

export { Skeleton, SkeletonItem };

export default {
  title: "Skeleton 骨架屏",
  category: "数据展示",
  status: "已完成",
  install(app: App): void {
    app.use(Skeleton as any);
  },
};

参考

  1. 非 Prop 的 Attribute
  2. Vue3 JSX 使用指南

闲谈

秀下 DevUI 发的抱枕(掘金的徽章啥时候才能到呢