空状态组件设计与实现的实战经验分享

Air-晨曦 组件 阅读 711
赞 31 收藏
二维码
手机扫码查看
反馈

我的写法,亲测靠谱

空状态组件这东西,看着简单,真在项目里用起来,坑可真不少。我最早就是随便搞个 div 套句话完事,后来被产品怼了好几次:这个太丑了、没情感、用户不知道下一步干嘛……折腾了几轮后,现在我做 Empty 的方式基本固定了,分享出来,省得你再踩一遍。

空状态组件设计与实现的实战经验分享

我现在写的 Empty 组件长这样:

<div class="empty-state">
  <img src="/assets/empty-box.svg" alt="空状态图标" class="empty-icon" />
  <p class="empty-title">暂无数据</p>
  <p class="empty-description">当前列表中没有符合条件的内容,请尝试调整筛选条件</p>
  <button class="btn btn-primary empty-action" @click="handleRefresh">刷新一下</button>
</div>
.empty-state {
  padding: 64px 20px;
  text-align: center;
  color: #666;
  font-size: 14px;
}

.empty-icon {
  width: 120px;
  height: 120px;
  margin-bottom: 16px;
  opacity: 0.7;
}

.empty-title {
  font-size: 16px;
  font-weight: 500;
  color: #333;
  margin-bottom: 8px;
}

.empty-description {
  color: #999;
  line-height: 1.5;
  margin-bottom: 20px;
}

.empty-action {
  padding: 8px 16px;
  font-size: 14px;
}

这种结构我用了快三年,从 Vue 到 React 都适用。关键点在于:结构清晰 + 语义明确 + 可交互。很多人只把它当展示组件,但我觉得它必须带“出路”——要么引导操作,要么提示原因。

比如上面的 handleRefresh,不只是刷新页面,很多时候是重新拉接口。我一般会把这类逻辑封装成 props 传进来,方便复用。

别再这么写了,我都替你看累了

下面这些写法,我在 Code Review 时见一次劝一次,真的别再用了。

  • 纯文字提示:“暂无数据” —— 用户看到就懵了,是不是出错了?还是本来就该这样?啥也不说清。
  • 图标太大或太小:有人放个 300px 的图,整个页面都被占满了;也有人用 20px 小 icon,根本看不见。
  • 按钮缺失或乱放:比如加了个“新增”按钮,但当前场景根本不支持新增,这不是误导吗?
  • 直接用 alert 或 toast 提示“无数据”:这是最离谱的,用户体验极差,关掉还要点一下。

还有一个常见错误是动态渲染时机不对。比如我见过这种写法:

// 错误示范
if (data.length === 0) {
  return <Empty />;
}
return <List data={data} />;

问题在哪?如果请求还没回来,data 是 undefined 或 null,这时候就会直接进 Empty,但实际上只是 loading 状态。正确做法是:

if (loading) {
  return <Loading />;
}
if (data && data.length === 0) {
  return <Empty />;
}
return <List data={data} />;

注意这里判断 data 是否存在。我踩过好几次这个坑,接口失败时 data 没定义,直接 .length 报错,页面白屏。所以一定要先判空。

实际项目中的坑

我们有个管理后台,列表页用的是分页查询。一开始设计是:第一页没数据才显示 Empty,后面翻页没数据就不显示了。结果上线后用户反馈“翻着翻着列表没了”,因为第二页返回空数组,前端以为还有数据,但其实已经到底了。

后来改成了:只要当前页返回空数组,并且不是 loading 状态,就显示 Empty。同时加上一句“已加载全部内容”之类的描述,避免误解。

另一个问题是国际化。早期我们把文案写死在组件里,后来多语言一上,全乱套了。现在我的方案是把文本抽成 props:

<Empty
  title="No Data"
  description="Try adjusting your filters"
  actionText="Refresh"
  @action="handleRefresh"
/>

或者更进一步,用 i18n key:

const messages = {
  en: {
    empty_title: 'No Data',
    empty_desc: 'Try adjusting your filters'
  },
  zh: {
    empty_title: '暂无数据',
    empty_desc: '请尝试调整筛选条件'
  }
}

然后通过 $t('empty_title') 动态渲染。这套流程跑顺了之后,换语言基本不改结构。

还有一点容易忽略:**SEO 和无障碍访问(a11y)**。虽然 Empty 多数出现在 SPA 里,但图标要加 alt,文字要有层次(title 用 h3 或 strong,desc 用普通 p),按钮要有可访问性标签。别小看这些,有些客户真会拿工具扫 accessibility 问题。

要不要加动效?我建议谨慎

有次产品经理非要在 Empty 里加个飘动画的小纸飞机,说是“增加趣味性”。我实装后发现,首屏加载时那个动画会闪一下,体验反而更糟。而且低端安卓机上帧率直接掉到 20fps。

最后妥协方案是:只有首次进入且确实为空时才播放一次动画,后续刷新不再播放。通过 localStorage 记录状态,或者路由参数控制。

所以我的建议是:动效可以有,但必须满足三个条件:

  • 不影响首屏性能
  • 不会重复干扰用户
  • 不是为了炫技而加

否则不如静态图来得稳。

API 设计我也琢磨明白了

我现在封装的 Empty 组件,props 基本长这样:

props: {
  type: { type: String, default: 'data' }, // data / search / network 等类型
  image: { type: String, default: '' },     // 自定义图片
  title: { type: String, required: true },
  description: { type: String },
  showAction: { type: Boolean, default: false },
  actionText: { type: String, default: '操作' },
  loading: { type: Boolean, default: false } // 防止和 loading 混淆
}

其中 type 很实用。比如搜索无结果和网络错误,展示内容应该不同。我可以根据 type 自动映射默认文案和图标:

const EMPTY_CONFIGS = {
  data: {
    image: '/empty-data.svg',
    title: '暂无数据',
    desc: '当前没有内容'
  },
  search: {
    image: '/empty-search.svg',
    title: '未找到结果',
    desc: '换个关键词试试?'
  },
  network: {
    image: '/empty-network.svg',
    title: '网络异常',
    desc: '检查网络后重试',
    actionText: '重试'
  }
}

这样调用的时候就很简单了:

<Empty v-if="error" type="network" @action="fetchData" />
<Empty v-else-if="list.length === 0" type="data" />

既减少重复代码,又保证一致性。

最后一点:别让 Empty 背锅

有一次线上报警,用户打不开页面,一看是 Empty 组件报错了。查了半天发现是因为某个环境下 require 图片路径失败,导致组件渲染中断。

现在的处理方式是:所有资源都 fallback。比如图片加载失败:

<img
  :src="imagePath"
  @error="useDefaultImage"
  alt="空状态"
/>
methods: {
  useDefaultImage(e) {
    e.target.src = '/default-empty.png';
  }
}

另外,组件本身要足够健壮。即使没传 title,也不能崩。我会加默认值,至少让用户看到“空状态”三个字,而不是一片空白。

以上是我总结的最佳实践

Empty 看似是个小组件,但真要把体验做细,要考虑加载顺序、错误边界、可访问性、多语言、复用性一堆问题。我现在这套写法,经过三四个大项目验证,基本没出过幺蛾子。

当然也不是最优解。比如服务端渲染时的样式隔离、微前端下的资源引用,还有优化空间。但对大多数项目来说,够用、稳定、好维护才是第一位。

以上是我踩坑后的总结,希望对你有帮助。有更好的实现方式欢迎评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论