原文链接:Behind the ‘as’ prop: polymorphism done well — Kristóf Poduszló
相关笔记:《React 中的多态性及其两种模式》

组件多态性的实现之一:根据 props 属性渲染不同的 HTML,这里指 as 属性

案例使用 Button 组件来实现,默认情况下 Button 组件应该渲染的是一个 button 元素,但是考虑到语义化和可访问性,有时我们需要将其渲染为 a 标签,例如按钮的作用是点击跳转时。

使用 as 属性来实现具有多态性的Button组件的基础:

import { clsx } from "clsx/lite";
 
function MyButton({ as = "button", className, ...props }) {
	const Element: React.ElementType = as;
	return (
		<Element
			className={clsx(className, "my-button")}
			{...props}
		/>
	);
}

WARNING

  1. 无法直接使用 props as 渲染 JSX - ; // Renders <as> ❌ — via jsx("as", {}),因此将 as 属性赋值给 Element
  2. 定义 Element 类型为 React.ElementType

处理 TypeScript 类型

import { clsx } from "clsx/lite";
 
type Merge<T, U> = DistributiveOmit<T, keyof U> & U;
type DistributiveOmit<
	T,
	K extends PropertyKey,
> = T extends unknown ? Omit<T, K> : never;
 
type MyButtonElementType = React.ElementType<
	any,
	"button" | "a"
>;
 
type MyButtonProps<T extends MyButtonElementType = "button"> =
	Merge<
		React.ComponentPropsWithoutRef<T> & { as?: never },
		{
			as?: T;
			className?: string;
		}
	>;
 
function MyButton<T extends MyButtonElementType = "button">({
	as = "button" as T,
	className,
	...props
}: MyButtonProps<T>) {
	const Element: MyButtonElementType = as;
	return (
		<Element
			className={clsx(className, "my-button")}
			{...props}
		/>
	);
}

Button 组件完善类型支持

  1. as 属性并未必填项目,默认值为 button
  2. 控制可传入的 as 属性类型 buttona
  3. 假设默认值 "button" 可以赋值给 T ,即使明确设置类型参数如 <MyButton<"a">> 也会使其无效。