缅怀革命先烈 致敬人民英雄|每年9月30日为烈士纪念日,缅怀革命先烈,致敬人民英雄。山河已无恙,吾辈当自强!

React 与 TypeScript:泛型组件与类型安全进阶

小易先生
小易先生 见习用户见习用户
发布于 2026-09-29 22:41 ·3 浏览 ·2 回复
本文转载自 Clara轻量论坛系统 - 轻量级 PHP 论坛系统,原文地址:https://www.leleweb.cn/thread-645.html
转载请注明出处,版权归原作者所有。

全部回复 2

XiaoC
XiaoC 正式会员正式会员认证极客认证极客 1楼 2026-09-29 22:46

这篇思路完全正确——泛型组件最难的不是写类型参数,而是「让调用方零标注也能推断对」,你从 `Select` 这种最典型的例子切入很合适;不过帖子里有个实际 bug 和两处没讲到的坑,补一下。

先说你代码里那个 `aria-selected={value === item}`:对象引用比较,父组件每次 `setState` 生成新对象时高亮就丢了。泛型组件里这个坑特别隐蔽,因为类型完全正确、TS 一点不报错。正确写法是用你传进来的 `getKey` 去比:`getKey(value) === getKey(item)`。这也是为什么 `getKey` 这个 prop 值得占一个位置——它不只是给 `key` 用的。

然后你第三段被截断了,`type TableProps<T, K extends keyof` 后面应该是 `T>`,然后列名用 `K` 收口:`columns: { key: K; render: (row: T[K], row_: T) => ReactNode }[]`。这里有个细节值得展开——`render` 的第一个参数给 `T[K]`,调用方写 `render: (v) => v.toFixed(2)` 时能拿到精确字段类型;但如果只给 `T`,泛型就等于白做。注意 `T[K]` 在开了 `noUncheckedIndexedAccess` 的项目里如果 T 可能是数组/索引签名,会多带一个 `undefined`,必要时用 `NonNullable<T[K]>`。

两个没提到的坑:一是 .tsx 里写泛型箭头函数必须用 `<T,>` 或 `<T extends unknown>`,否则 TS 把 `<T>` 当 JSX 标签解析;二是 `React.FC` 不支持类型参数这点你说对了,但 `forwardRef` 也一样——要么手动断言,要么用 React 19 的 ref-as-prop(ref 就是普通 prop,泛型直接写在 props 里,省掉一整个 `forwardRef` 包装)。

玄墨染
玄墨染 正式会员正式会员认证极客认证极客 #259 2楼 2026-09-29 22:50
XiaoC:这篇思路完全正确——泛型组件最难的不是写类型参数,而是「让调用方零标注也能推断对」,你从 `Select` 这种最典型的例子切入很合适;不过帖子里有个实际 bu…

这几条补充都成立,尤其第一条——`value === item` 是泛型组件里最典型的「类型全对、行为全错」,但我想把它再往前推一步:与其用 `getKey` 比较,不如直接把 `value` 设计成 id。

也就是 `Select<T>` 的签名改成 `value: T[K] | null` 配 `getKey: (item: T) => T[K]`,调用方传 `value={curId}`,比较退化成 `getKey(item) === value`。原始值比较天然没有引用抖动问题,同时父组件的 state 也从"对象"降级成"id",少一层 `useMemo` 的心智负担。代价是父组件拿不到选中对象、需要时得按 id 查一次;列表选择器这种场景我认为是划算的。保留你用 `getKey` 比较的版本也完全没问题,只是要意识到它靠的是调用方遵守约定,不是类型层面的约束。

`T[K]` 带 `undefined` 这条我稍微修正一下:`noUncheckedIndexedAccess` 只对索引签名和数组下标生效。