在Next.js+TypeScript中扩展next/link时出现$$typeof属性缺失错误
我希望基于Next.js的next/link创建链接API,编写了自定义Link组件,代码如下:
import NextLink from 'next/link' import ExternalLink from './External' type PropsType = typeof NextLink & { href: string children: React.ReactNode } export default function Link(props: PropsType) { return <NextLink {...props} /> } Link.External = ExternalLink
ExternalLink组件代码为:
import React from 'react' type ComponentPropsType = React.HTMLProps<HTMLAnchorElement> & { children: JSX.Element href: string } export default function Component({ href, children, ...attrs }: ComponentPropsType) { return <a {...attrs} target="_blank" rel="noreferrer" href={href}>{children}</a> }
仅使用常规链接<Link href="/foo">Foo</Link>时,出现TypeScript错误:
Property '$$typeof' is missing in type '{ children: string; href: string; }' but required in type 'ForwardRefExoticComponent<Omit<AnchorHTMLAttributes, keyof InternalLinkProps> & InternalLinkProps & { ...; } & RefAttributes<...>>'.ts(2741)
Next.js的link.d.ts定义如下:
/// <reference types="node" /> import React from 'react'; import { UrlObject } from 'url'; declare type Url = string | UrlObject; declare type InternalLinkProps = { href: Url; as?: Url; replace?: boolean; scroll?: boolean; shallow?: boolean; passHref?: boolean; prefetch?: boolean; locale?: string | false; legacyBehavior?: boolean; /** * requires experimental.newNextLinkBehavior */ onMouseEnter?: (e: any) => void; /** * requires experimental.newNextLinkBehavior */ onTouchStart?: (e: any) => void; /** * requires experimental.newNextLinkBehavior */ onClick?: (e: any) => void; }; export declare type LinkProps = InternalLinkProps; declare const Link: React.ForwardRefExoticComponent<Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, keyof InternalLinkProps> & InternalLinkProps & { children?: React.ReactNode; } & React.RefAttributes<HTMLAnchorElement>>; export default Link;
请问当前写法哪里有误?如何正确进行类型定义以实现组件扩展?
错误原因
你错误地将typeof NextLink(即组件本身的ForwardRefExoticComponent类型)当成了组件的props类型来使用。typeof NextLink代表的是整个Link组件的类型,而非它接收的props类型,这就导致传递普通的{href, children}对象时,无法满足组件类型的要求(比如缺少$$typeof这个React内部标识)。
正确的类型定义与组件扩展方式
1. 复用NextLink的Props类型
Next.js已经导出了LinkProps类型,我们可以直接复用它,或者通过React.ComponentProps<typeof NextLink>来获取NextLink的完整props类型,无需自己重新定义。
2. 修正自定义Link组件
将PropsType改为NextLink的props类型,同时正确处理组件的静态属性类型:
import NextLink from 'next/link' import ExternalLink from './External' import type { ComponentProps } from 'react' // 获取NextLink的完整props类型 type LinkProps = ComponentProps<typeof NextLink> // 定义自定义Link组件的类型,包含静态的External属性 type CustomLinkComponent = typeof NextLink & { External: typeof ExternalLink } export default function Link(props: LinkProps) { return <NextLink {...props} /> } // 类型断言确保静态属性赋值符合类型要求 ;(Link as CustomLinkComponent).External = ExternalLink
3. 修正ExternalLink组件的类型
原ExternalLink的children类型限制为JSX.Element过于严格,应该改为React.ReactNode以支持文本等非元素类型;同时推荐使用React.AnchorHTMLAttributes<HTMLAnchorElement>代替HTMLProps,类型更精准:
import React from 'react' type ExternalLinkProps = React.AnchorHTMLAttributes<HTMLAnchorElement> & { href: string children: React.ReactNode } export default function ExternalLink({ href, children, ...attrs }: ExternalLinkProps) { return <a {...attrs} target="_blank" rel="noreferrer" href={href}>{children}</a> }
4. 简化写法(可选)
如果不需要额外约束,也可以直接让自定义Link组件的props完全继承NextLink的类型,无需额外定义:
import NextLink from 'next/link' import ExternalLink from './External' // 直接使用NextLink的props类型 export default function Link(props: React.ComponentProps<typeof NextLink>) { return <NextLink {...props} /> } // 类型断言处理静态属性 ;(Link as typeof NextLink & { External: typeof ExternalLink }).External = ExternalLink
这样修改后,使用<Link href="/foo">Foo</Link>就不会再出现TypeScript错误,同时也正确扩展了Link.External静态组件。
内容的提问来源于stack exchange,提问作者Lance Pollard

