uni-ui组件库实战踩坑与性能优化经验分享
先看效果,再看代码
最近在搞一个 uni-app 的小程序项目,UI 组件库直接选了 uni-ui。不是因为多高大上,而是它和 uni-app 官方深度绑定,打包体积小、兼容性好,尤其在微信小程序里跑得稳。我一开始是想用 uView,但发现有些组件在 H5 和小程序表现不一致,折腾半天不如直接用官方的。
今天不讲怎么安装(npm install 一敲就完事),直接上干货:怎么用 uni-ui 做一个带下拉刷新 + 上拉加载的列表页,还带个搜索框和空状态提示。这几乎是每个 App 都有的场景。
<template>
<view class="page">
<uni-search-bar @confirm="handleSearch" v-model="searchKeyword" />
<view class="list-container" v-if="list.length > 0">
<uni-list>
<uni-list-item
v-for="(item, index) in list"
:key="index"
:title="item.name"
:note="item.desc"
clickable
@click="goDetail(item.id)"
/>
</uni-list>
</view>
<view class="empty-tips" v-else>
<text>暂无数据</text>
</view>
<!-- 加载更多提示 -->
<uni-load-more
:status="loadStatus"
@clickLoadMore="loadMore"
/>
</view>
</template>
别看这段代码简单,里面有几个坑我踩过不止一次。
踩坑提醒:这三点一定注意
第一,uni-search-bar 的 v-model 必须是字符串类型。 我之前把 searchKeyword 初始化成 null,结果输入框根本没法输入,控制台也不报错,查了半天才发现 uni-ui 内部对非字符串值做了拦截。现在我一律初始化为 '':
data() {
return {
searchKeyword: '',
list: [],
page: 1,
loadStatus: 'more' // more / loading / noMore
}
}
第二,uni-list-item 的 clickable 属性不是可选项,是必须加的。 不加的话,在 iOS 小程序里点击没反馈,用户以为卡了。加上之后才有 hover 效果和点击波纹(虽然波纹只在 H5 有,但至少小程序会有轻微变色)。
第三,uni-load-more 的 status 控制逻辑要自己写清楚。 它不会自动判断是否还有下一页,你得在接口返回后手动改状态。比如:
async loadMore() {
if (this.loadStatus === 'loading' || this.loadStatus === 'noMore') return;
this.loadStatus = 'loading';
try {
const res = await fetch(https://jztheme.com/api/list?page=${this.page + 1});
const data = await res.json();
if (data.list && data.list.length > 0) {
this.list = [...this.list, ...data.list];
this.page += 1;
this.loadStatus = 'more';
} else {
this.loadStatus = 'noMore';
}
} catch (err) {
this.loadStatus = 'more'; // 失败后允许重试
uni.showToast({ title: '加载失败', icon: 'none' });
}
}
这里注意:catch 里我把状态改回 'more',这样用户还能点“点击加载”重试。如果直接设成 'noMore',用户就没法再试了——这种细节体验很重要。
这个场景最好用:自定义空状态
uni-ui 没有提供空状态组件,但实际项目中几乎每个列表都要处理。我的做法是:用 v-if 判断 list.length === 0,然后放个自定义的空提示区域。
<view class="empty-tips" v-if="list.length === 0 && !loading">
<image src="/static/empty.png" mode="widthFix" class="empty-img" />
<text class="empty-text">什么都没找到</text>
<button class="reload-btn" @click="reload">重新加载</button>
</view>
这里有个小技巧:**一定要加 !loading 条件**。否则首次进入页面时,list 是空数组,会立刻显示空状态,而此时数据其实正在加载中,体验很割裂。所以我一般还会加个 loading 状态控制:
async fetchData() {
this.loading = true;
try {
const res = await fetch(https://jztheme.com/api/list?page=1);
this.list = (await res.json()).list || [];
} finally {
this.loading = false;
}
}
高级技巧:动态切换主题色
uni-ui 的组件样式是通过 CSS 变量控制的,比如 --uni-color-primary。如果你想在运行时切换主题(比如夜间模式),可以直接修改根节点的 CSS 变量。
// 切换深色主题
switchTheme(dark) {
const root = uni.getSystemInfoSync().platform === 'ios'
? document.documentElement
: document.body;
if (dark) {
root.style.setProperty('--uni-bg-color', '#121212');
root.style.setProperty('--uni-text-color', '#ffffff');
root.style.setProperty('--uni-color-primary', '#BB86FC');
} else {
root.style.setProperty('--uni-bg-color', '#ffffff');
root.style.setProperty('--uni-text-color', '#333333');
root.style.setProperty('--uni-color-primary', '#007AFF');
}
}
不过要注意:**这个方法在小程序里无效**。因为小程序没有 DOM 操作权限。如果你要做小程序的主题切换,得靠条件 class 或者动态 style 绑定。比如:
<view :class="{'dark-theme': isDark}" class="page">
<uni-list-item
:style="{ backgroundColor: isDark ? '#1e1e1e' : '#fff' }"
...
/>
</view>
虽然麻烦点,但兼容性好。我现在的项目就是 H5 用 CSS 变量,小程序用 class 切换,一套逻辑两套实现。
别被文档骗了:有些组件其实不推荐用
uni-ui 文档里列了一堆组件,但有些真的别碰。比如 uni-data-picker,在微信小程序里层级问题严重,经常被原生 input 盖住;还有 uni-swipe-action,滑动逻辑和 iOS 手势冲突,导致页面无法正常滚动。
我的建议是:**核心交互用原生组件 + uni-ui 基础组件组合**。比如侧滑删除,我宁愿自己用 touchstart/touchmove 手写,也不用 uni-swipe-action。虽然多写 20 行代码,但稳定得多。
另外,uni-icons 图标库很全,但注意:**图标名称大小写敏感**。比如 icon-home 可以,icon-Home 就不显示。我曾经因为这个调试了半小时,最后发现是文档示例写错了(他们 GitHub 上有人提过 issue,但还没改)。
结尾碎碎念
uni-ui 不是银弹,但它在“够用+稳定”之间找到了平衡点。尤其适合赶工期、多端发布、不想折腾兼容性的项目。我用它做了三个上线项目,除了偶尔的小坑,整体体验比第三方 UI 库省心多了。
以上是我踩坑后的总结,希望对你有帮助。这个技术的拓展用法还有很多(比如结合 Vuex 做全局状态管理、自定义 uni-ui 主题包等),后续会继续分享这类博客。有更优的实现方式欢迎评论区交流,毕竟前端这行,谁还没被 UI 库坑过几次呢?

暂无评论