You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Next.js Link与TypeScript的多态Button组件类型错误修复

修复多态Button组件的TypeScript类型错误

问题背景

我正在构建一个多态Button组件,可根据as属性渲染Next.js的Link或原生button元素。现有代码如下:

import { ComponentPropsWithoutRef, ElementType, ReactNode } from "react";
import Link from "next/link";

type OwnProps<T extends ElementType = ElementType> = {
  as?: T;
  variant?: "primary" | "secondary" | "neutral" | "danger" | "text";
};

type Props<T extends ElementType> = OwnProps<T> & Omit<ComponentPropsWithoutRef<T>, keyof OwnProps>;

export default function Button<T extends ElementType = "button" | "a">({ 
  as,
  variant = "primary",
  ...props
}: Props<T>) {
  const Component = as === "a" ? Link : "button";

  return <Component {...props} />;
}

代码可正常运行编译,但TypeScript在<Component {...props} />处抛出如下错误:

Type 'Omit<Props<T>, "as" | "variant">' is not assignable to type 'IntrinsicAttributes & Omit<AnchorHTMLAttributes<HTMLAnchorElement>, keyof InternalLinkProps> & ... 4 more ... & ButtonHTMLAttributes<...>'.
  Property 'href' is missing in type 'Omit<Props<T>, "as" | "variant">' but required in type 'InternalLinkProps'.ts(2322)
link.d.ts(11, 5): 'href' is declared here.

问题核心:Next.js的LinkProps要求href为必填项,但当前类型定义中该属性可能为undefined(因为button元素不需要它)。

最佳修复方案

方案1:条件类型约束(推荐)

通过条件类型让TypeScript根据as的值自动校验必填属性,修改类型定义:

import { ComponentPropsWithoutRef, ElementType } from "react";
import Link from "next/link";
import type { LinkProps } from "next/link";

type OwnProps = {
  variant?: "primary" | "secondary" | "neutral" | "danger" | "text";
};

// 针对不同的as类型定义对应的props
type Props<T extends ElementType> = 
  T extends "a" 
    ? OwnProps & Omit<LinkProps, keyof OwnProps>
    : T extends "button"
      ? OwnProps & Omit<ComponentPropsWithoutRef<"button">, keyof OwnProps>
      : OwnProps & Omit<ComponentPropsWithoutRef<T>, keyof OwnProps>;

export default function Button<T extends ElementType = "button">({ 
  as,
  variant = "primary",
  ...props
}: Props<T>) {
  const Component = as === "a" ? Link : as ?? "button";

  return <Component {...props as any} />;
}

说明:

  • 当as为"a"时,Props会继承LinkProps,强制要求传入href
  • 当as为"button"时,Props使用原生button的属性,不需要href
  • 支持扩展其他ElementType,保持组件的多态性

方案2:函数重载

通过函数重载明确不同as对应的props规则,让TypeScript做精准推断:

import { ComponentPropsWithoutRef } from "react";
import Link from "next/link";
import type { LinkProps } from "next/link";

type OwnProps = {
  variant?: "primary" | "secondary" | "neutral" | "danger" | "text";
};

// 重载1:渲染原生button
export default function Button(props: OwnProps & ComponentPropsWithoutRef<"button">): JSX.Element;
// 重载2:渲染Next.js Link(对应a标签)
export default function Button(props: OwnProps & Omit<LinkProps, "as">): JSX.Element;
// 通用实现
export default function Button({ 
  as,
  variant = "primary",
  ...props
}: any) {
  const Component = as === "a" ? Link : "button";

  return <Component {...props} />;
}

说明:

  • 为两种核心场景分别定义重载签名,TypeScript会根据传入的props自动匹配对应的类型
  • 实现部分使用any简化类型处理,适合不需要支持大量自定义ElementType的场景

方案3:精准类型断言

如果不想修改原有类型结构,可在组件内部根据as的值做类型断言:

import { ComponentPropsWithoutRef, ElementType } from "react";
import Link from "next/link";
import type { LinkProps } from "next/link";

type OwnProps<T extends ElementType = ElementType> = {
  as?: T;
  variant?: "primary" | "secondary" | "neutral" | "danger" | "text";
};

type Props<T extends ElementType> = OwnProps<T> & Omit<ComponentPropsWithoutRef<T>, keyof OwnProps>;

export default function Button<T extends ElementType = "button" | "a">({ 
  as,
  variant = "primary",
  ...props
}: Props<T>) {
  if (as === "a") {
    return <Link {...props as LinkProps} />;
  }
  return <button {...props as ComponentPropsWithoutRef<"button">} />;
}

说明:

  • 通过分支判断分别处理Link和button的渲染,同时用类型断言告诉TypeScript当前props符合对应组件的要求
  • 这种方式代码改动最小,但需要确保分支逻辑和断言的类型一致,避免运行时错误

内容的提问来源于stack exchange,提问作者Andreas Remdt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 02:32:48