TDesign Mobile 组件库实战与移动端开发优化技巧
我的写法,亲测靠谱
用 TDesign Mobile 有段时间了,从一开始照着文档抄,到后来自己踩了一堆坑,现在终于能写出不那么别扭的代码了。今天就分享几个我反复验证过的写法,特别是那些“看起来没问题但实际一上线就炸”的场景。
首先说个最常用的:表单提交。很多人直接用 <Form> 包裹一堆 <Input>,然后在 submit 里取值。但我在项目里发现,如果用户输入后快速切换页面,或者组件被销毁,某些情况下 formData 会变成空对象。后来我改成了手动绑定每个字段的 value 和 onChange,虽然啰嗦点,但稳得多。
// 我现在的写法
const [form, setForm] = useState({ name: '', phone: '' });
const handleChange = (key, val) => {
setForm(prev => ({ ...prev, [key]: val }));
};
return (
<Form>
<FormItem label="姓名">
<Input
value={form.name}
onChange={(val) => handleChange('name', val)}
/>
</FormItem>
<FormItem label="手机号">
<Input
value={form.phone}
onChange={(val) => handleChange('phone', val)}
/>
</FormItem>
</Form>
);
这种写法虽然多写了两行,但避免了 Form 内部状态和外部状态不同步的问题。尤其在复杂表单(比如带动态增减字段)时,TDesign 的 Form 组件内部状态管理容易出岔子,不如自己掌控。
这几种错误写法,别再踩坑了
我见过太多人这么干:
// 错误示范1:直接在 render 里调用函数
<Form>
<FormItem label="测试">
<Input onChange={someAsyncHandler()} /> // 注意这里加了括号!
</FormItem>
</Form>
这会导致每次渲染都执行 someAsyncHandler(),不仅性能差,还可能触发不必要的副作用。正确的应该是传函数引用:onChange={someAsyncHandler}。
还有更隐蔽的坑:在 Popup 或 ActionSheet 里直接放异步操作。
// 错误示范2:在 Popup 内直接 fetch
<Popup visible={show}>
<Button onClick={() => {
fetch('https://jztheme.com/api/submit')
.then(res => res.json())
.then(data => console.log(data));
}}>
确认
</Button>
</Popup>
问题在于,如果用户点了按钮但网络慢,这时候他关掉弹窗,组件卸载了,但 fetch 还在跑,后续的 .then 可能会报错(比如 setState on unmounted component)。我吃过这个亏,后来统一加了取消逻辑:
// 改进版
useEffect(() => {
const controller = new AbortController();
if (show && shouldFetch) {
fetch('https://jztheme.com/api/submit', { signal: controller.signal })
.then(res => res.json())
.then(data => {
// 处理数据
})
.catch(e => {
if (e.name !== 'AbortError') console.error(e);
});
}
return () => controller.abort();
}, [show, shouldFetch]);
虽然麻烦点,但至少不会在控制台刷一堆红。
实际项目中的坑
上周刚遇到一个诡异问题:在 iOS 上,Picker 组件滚动后,选中的值没变,但视觉上高亮了。查了半天,发现是 value 没及时更新。TDesign 的 Picker 要求 value 必须和 options 里的值严格相等(包括类型)。我传的是字符串,但 options 里是数字,结果就卡住了。
所以现在我养成了习惯:所有传给 TDesign 组件的值,先做类型对齐。
// 确保类型一致
const options = [
{ label: '选项1', value: '1' },
{ label: '选项2', value: '2' }
];
// 即使后端返回 number,也转成 string
const [selected, setSelected] = useState(String(initialValue));
另一个头疼的是 Tab 切换时的性能问题。默认情况下,TDesign 的 Tab 会缓存所有 pane,但如果每个 pane 都加载大量数据(比如列表),内存占用会飙升。我试过加 destroyOnHide,但发现它只在首次隐藏时销毁,再次显示又重建,体验反而更差。
最后妥协方案:只缓存当前 tab 和相邻 tab,其他手动 unmount。虽然 hacky,但内存稳了。
// 根据 activeTabIndex 决定是否渲染
{activeTab === 0 && <TabPane1 />}
{activeTab === 1 && <TabPane2 />}
{activeTab === 2 && <TabPane3 />}
我知道这不是最优解,但比整个页面卡死强。
关于主题定制的血泪史
别信文档里说的“一行配置换主题”。我在一个项目里要适配深色模式,以为改个 CSS 变量就行,结果发现 TDesign Mobile 的部分组件(比如 Stepper 的按钮)用了硬编码颜色,变量覆盖不了。
折腾半天,最后只能全局覆盖样式:
/* 强制覆盖 stepper 按钮 */
.t-steps-item__btn {
background-color: var(--td-bg-color-component) !important;
border-color: var(--td-border-color) !important;
}
加了 !important 才生效。虽然不优雅,但赶工期的时候哪管那么多。建议:如果要做深度定制,提前检查所有组件的样式源码,别等上线前才发现漏了某个角落。
结尾碎碎念
总的来说,TDesign Mobile 在基础组件上很省事,但一旦涉及复杂交互或定制,就得自己兜底。我的经验是:简单场景放心用,复杂场景别偷懒,该自己管状态就自己管,该加防抖就加防抖。
以上是我踩坑后的总结,希望对你有帮助。有更好的方案欢迎评论区交流——毕竟前端这行,谁还没被 UI 库坑过几次呢?

暂无评论