Typescript属性在扩展类型上不存在。

8
我有一个React组件,根据我想要将用户重定向到应用内部还是外部URL,它会使用to (react-router-dom属性)或href 属性。 TypeScript 报错了:

属性“to”在类型“LinkType”上不存在。

属性“href”在类型“LinkType”上不存在。

这是我定义类型的方式:

import React from 'react';
import { Link } from 'react-router-dom';

interface LinkBase {
  label: string;
  icon?: JSX.Element;
}

interface RouterLink extends LinkBase {
  to: string;
}

interface HrefLink extends LinkBase {
  href: string;
}

type LinkType = RouterLink | HrefLink;

export function NavLink(props: LinkType) {
  const { to, label, icon, href } = props;  // TS complains about those to and href

  return(
    {to ? (
      <Link to={to}>{label}</Link>
    ) : (
      <a href={href}>{label}</a>
    )}
  );
}

3
由于LinkType是一个联合类型,HrefLink和RouterLink都可以被赋值给LinkType。HrefLink上没有"to"属性,RouterLink上没有"href"属性。因此,TypeScript无法保证这些属性在联合类型中的存在。 - Alex D
@AlexD 已了解。我是否可以保留链接逻辑,同时解构 props 呢? - LazioTibijczyk
1
您可以访问联合类型的常规属性,比如标签和图标,但要获取其余两个属性,则需要基于条件缩小类型范围,参见类型守卫。 - Alex D
1
我提供了两种不同的方式来让 TypeScript 在类型检查方面运行正常,下面是示例。 - Alex D
1
只需将新的“LinkType”中的“|”替换为“&”。 - guzmanoj
2个回答

2
由于 LinkType 是一个联合类型,HrefLinkRouterLink 都可以分配给 LinkTypeHrefLink 上没有 "to",RouterLink 上没有 "href"。因此,typescript 无法保证联合上存在这两个属性中的任何一个。您可以使用类型守卫来完成您正在尝试的操作,同时满足 TypeScript 的要求。
import React from 'react';
import { Link } from 'react-router-dom';

interface LinkBase {
  label: string;
  icon?: JSX.Element;
}

interface RouterLink extends LinkBase {
  to: string;
}

interface HrefLink extends LinkBase {
  href: string;
}

type LinkType = RouterLink | HrefLink;

function isRouterLink(v: unknown): v is RouterLink {
  return v && Object.prototype.hasOwnProperty.call(v, 'to');
}

function isHrefLink(v: unknown): v is HrefLink {
  return v && Object.prototype.hasOwnProperty.call(v, 'href');
}

export function NavLink(props: LinkType) {
  const { label, icon } = props; 

  if (isRouterLink(props)) {
    const { to } = props; // typechecks now
    return (<> ... </>)
  }

  if (isHrefLink(props)) {
    const { href } = props;

    return (<> ... </>);
  }

  return (<> ... </>);
}

另一种选择是使用“标记”联合来避免需要类型保护函数。这些有不同的名称,具体取决于你与谁交流。其他可以找到这些的名称包括“辨别联合”和“不相交联合”等。
interface LinkBase {
  label: string;
  icon?: JSX.Element;
}

interface RouterLink extends LinkBase {
  type: 'router';
  to: string;
}

interface HrefLink extends LinkBase {
  type: 'href';
  href: string;
}

type LinkType = RouterLink | HrefLink;

export function NavLink(props: LinkType) {
  const { label, icon } = props; 

  if (props.type === 'router') {
    const { to } = props; // typechecks now
    return (<> ... </>);
  }

  if (props.type === 'href') {
    const { href } = props;

    return (<> ... </>);
  }

 return (<> ... </>); 
}

那么如果您检查LinkType['type']的类型,您会看到'router' | 'href',如果您在LinkType的此类型属性上执行if语句,TypeScript可以自动缩小类型。

是的。我也刚刚编辑了代码,因为我注意到我调用 hasOwnProperty 的方法是错误的,应该使用 Object.prototype.hasOwnProperty.call(...) 而不是 Object.prototype.hasOwnProperty(...) - Alex D
1
如果您需要处理联合类型中的更多类型,则过度设计和不可扩展。 - kari
我不确定我同意。另一种选择是将所有选项合并为单个类型,并使不同的属性可选,但我不认为这样更具可扩展性,因为您仍然必须在使用可选属性之前检查其是否已定义,否则可能会出现TypeScript无法静态检查的错误。 - Alex D

1
你可以像这样做:

type LinkType = RouterLink & HrefLink;

那样你就不必用代码缩小联盟范围。
编辑1。
Alex D提出了这种方法可能产生的副作用。基本上,您将无法定义LinkType对象而不满足所有属性(hreflabelto)。可以通过使用Partials或简单地将hrefto声明为可选项来解决这个问题。
interface HrefLink extends LinkBase {
  href?: string;
}

虽然这样可以通过类型检查,但我认为它在功能上并不是他想要的。将联合类型更改为交叉类型将需要将 hrefto 都放入 props 中,而不是其中一个。 - Alex D
1
@AlexD 的观点很有道理,但根据 OP 的代码,他只是像这样访问属性 const { to, label, icon, href } = props; 而不是创建一个新对象。 - guzmanoj
1
使用可选属性的问题在于,它会削弱类型。例如,如果他有一个HrefLink,现在就无法确保它具有href的类型安全性。但是,如果这不是他关心的问题,那么你的答案完全有效。 - Alex D
这一直是我的初始方法,只需使用可选参数,但这会削弱拥有RouterLink和HrefLink等2种类型的目的。我可以将这两个可选参数放在单个类型中,例如href?:string;to?:string; - LazioTibijczyk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接