u.id}
renderRow={(u, sel) => {u.name}}
selectedIds={selected}
onSelect={setId}
/>
```
### HTML 标签聚合组件(Polymorphic)
```tsx
// 基于 JSX.IntrinsicElements 实现 polymorphic component
type PolymorphicComponent = {
(
props: P & { as?: As } & JSX.IntrinsicElements[As]
): ReactNode;
};
// Button 可以是 button/div/a 等任何 HTML 元素
const Button: PolymorphicComponent<
{ variant?: "primary" | "secondary" },
"button"
> = ({ variant = "primary", as: Tag = "button", ...props }) => (
);
```
> [!warning] Polymorphic Component 的类型难点
>
> 上述写法是简化版。生产环境通常借助第三方库如 `@radix-ui/react-slot` 来处理复杂的泛型推导,因为要让 `as="a"` 时同时合并 `` 的属性(`href`, `target` 等)而不产生冲突,泛型约束较为复杂。
## Form 校验与 Zod Schema
现代 React + TS 项目中,推荐使用 **Zod** 等 Schema 库将校验逻辑和类型推导合二为一:
```tsx
import { z } from "zod";
// 1. 定义 Schema —— 类型从 schema 自动推导
const LoginSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
type LoginFormValue = z.infer;
// 2. React Hook Form + ZodResolver 无缝对接
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(LoginSchema),
});
// 3. 模板中使用 —— errors 和 register 都有完整类型提示
```
> [!tip] 为什么选择 Zod 而非 Yup?
>
> - Zod 用 TypeScript 原生语法定义,无需额外 import 类型(Yup 需要 `yupToZooooootTypes` 等桥接方案)
> - Zod 零依赖、性能更优,且支持 `.parse()` 运行时校验与 `.infer()` 类型推导一体化
> - Zod 的错误消息更可定制化
## 常见坑点与避坑指南
> [!failure] 坑1:`any` 的隐式传播
>
> `Array`、`Record` 会让整个链条失去类型保护。替代方案:
> - 用 `unknown` 替代顶层 `any`(必须先类型守卫才能使用)
> - 用泛型 `` 传递具体的数据结构
>
> ```tsx
> // ❌ 危险
> const data: Record = {};
> data.foo.bar.baz; // 编译通过,运行崩溃
>
> // ✅ 安全
> const data: Record = {};
> if (typeof data.foo === "object" && data.foo !== null) {
> data.foo.bar; // 需要额外的守卫
> }
> ```
> [!failure] 坑2:事件处理器中的类型收窄失效
>
> 箭头函数无法正确收窄:
> ```tsx
> // ❌ TS 无法推断 e 具体是哪个事件
>
>
> // ✅ 用命名函数保持类型信息
>
> // 但最好直接在属性上写,不包箭头
>
> ```
> [!failure] 坑3:useState 初始值的类型陷阱
>
> ```tsx
> // ❌ 类型变成 never[] —— TS 推断了最窄的空数组类型
> const [items, setItems] = useState([]);
> items.push("hello"); // error: Property 'push' does not exist on type 'never[]'
>
> // ✅ 显式声明泛型
> const [items, setItems] = useState([]);
>
> // ✅ 或者用 non-empty array 初始化
> const [items, setItems] = useState(["default"]);
> ```
> [!failure] 坑4:React.FC 的副作用
>
> `React.FC` 在早期被广泛推荐,但现在社区趋于弃用原因如下:
> - 它会隐式包含 `children` prop(即使你的组件不需要)
> - 不支持泛型
> - 使得 ReturnType 无法正确推导
> - 使 HOC 的 WrappedComponent 类型丢失
>
> ```tsx
> // ❌ 不推荐
> const MyComponent: React.FC = ({ children }) => {children}
;
>
> // ✅ 推荐
> function MyComponent({ children }: MyProps) {
> return {children}
;
> }
> ```
## 关联笔记
- [[hhs/REACT/README.md]]
- [[hhs/REACT/1. 基础篇/03-组件与 Props.md]]
- [[hhs/REACT/1. 基础篇/04-State 与不可变性.md]]
- [[hhs/REACT/3. 生态工具篇/08-路由管理.md]]
- [[hhs/REACT/3. 生态工具篇/09-状态管理.md]]