TDesign Mobile 组件库实战与移动端开发优化技巧

Designer°爱娜 移动 阅读 1,834
赞 22 收藏
二维码
手机扫码查看
反馈

我的写法,亲测靠谱

用 TDesign Mobile 有段时间了,从一开始照着文档抄,到后来自己踩了一堆坑,现在终于能写出不那么别扭的代码了。今天就分享几个我反复验证过的写法,特别是那些“看起来没问题但实际一上线就炸”的场景。

TDesign Mobile 组件库实战与移动端开发优化技巧

首先说个最常用的:表单提交。很多人直接用 <Form> 包裹一堆 <Input>,然后在 submit 里取值。但我在项目里发现,如果用户输入后快速切换页面,或者组件被销毁,某些情况下 formData 会变成空对象。后来我改成了手动绑定每个字段的 valueonChange,虽然啰嗦点,但稳得多。

// 我现在的写法
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}

还有更隐蔽的坑:在 PopupActionSheet 里直接放异步操作。

// 错误示范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 库坑过几次呢?

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

暂无评论