reactjs Typescript不接受组件声明中的联合类型

lfapxunr  于 2022-12-12  发布在  React
关注(0)|答案(1)|浏览(143)

我正在尝试创建一个采用onClickto属性的组件。

const StickyButton: React.FC<
  ({ onClick: MouseEventHandler } | { to: string }) & {
    buttonComponent?: ({ onClick: MouseEventHandler }) => JSX.Element
  } & BoxProps
> = ({
  children,
  onClick,
  to,
  buttonComponent: ButtonComponent = Button,
  ...props
}) => {
  const handler = !!to ? () => navigate(to) : onClick

  return (
    <StickyBox {...props}>
      <ButtonComponent onClick={handler}>{children}</ButtonComponent>
    </StickyBox>
  )
}

我收到以下TS错误。
TS 2339:类型“PropsWithChildren”上不存在属性“to”({ onClick:鼠标事件处理程序;}|{到:字符串;})& {按钮组件?:({单击:鼠标事件处理程序}:{ onClick:任意;})=〉元素;} & BoxProps〉'中找到的。
这是一个简化的TS游戏场
我试过了:

  • 删除React.FC类型并简单地输入解构后的props对象--结果相同
  • 删除除onClickto--{ onClick: MouseEventHandler } | { to: string }--之外的所有其他属性,则这两个属性都会得到相同类型的错误

我还尝试了一些更“完全定义”的联合类型:

interface BasicProps extends BoxProps {
  buttonComponent?: ({ onClick: MouseEventHandler }) => JSX.Element
}

interface LinkProps extends BasicProps {
  to: string
}

interface ButtonProps extends BasicProps {
  onClick: MouseEventHandler
}

但我得到的结果是一样的!
我希望不仅仅是我的TS编译器奇怪/慢🙃

zf9nrax1

zf9nrax11#

在我看来,一切都在按预期工作。TypeScript的结构类型系统只会检查必需的属性是否存在,而不会检查额外的属性是否存在:

type Foo = { bar: string }
function doSomething(thing: Foo) { … }

// Not explicitly a `Foo`
const foo = { bar: "barbarbar", baz: "bazbazbaz" }

// This is allowed since the type of `foo` extends `Foo`
doSomething(foo);

TypeScript的freshness概念可能会引起混淆,也称为 * 严格对象文字检查 *。通过新鲜度检查,TypeScript实际上会检查对象文字是否仅定义已知属性:

type Foo = { bar: string }
function doSomething(thing: Foo) { … }

// Allowed, only known properties defined:
const aFoo: Foo = { bar: "barbarbar" }
// This is also, by necessity, allowed:
doSomething({ bar: "barbarbar" })

// Not allowed with strict object literal checking:
const bFoo: Foo = { bar: "barbarbar", baz: "baz" }
//                                    ~~~~~~~~~~
//                                    Type '{ bar: string; baz: string; }' is not assignable to type 'Foo'.
//                                    Object literal may only specify known properties, and 'baz' does not exist in type 'Foo'.
//                                    (2322)

// Also not allowed:
doSomething({ bar: "barbarbar", baz: "baz" })
//                              ~~~~~~~~~~
//                              Argument of type '{ bar: string; baz: string; }' is not assignable to parameter of type 'Foo'.
//                              Object literal may only specify known properties, and 'baz' does not exist in type 'Foo'.
//                              (2345)

这里令人困惑的是已知属性和“额外”属性的分离。对于类型联合,可能存在 * 已知 * 但额外的属性:

type A = { a: string }
type B = { b: string }

type U = A | B

// Both allowed:
const aFoo: U = { a: "foo" }
const bFoo: U = { b: "bar" }

// But so is this, as all properties are known ones,
// even though one of them is not strictly needed to fit the shape of the type:
const cFoo: U = { a: "foo", b: "bar" }

// But this will fail with the extra property `c`
const dFoo: U = { a: "foo", b: "bar", c: "baz" }
//                                    ~~~~~~~~
//                                    Type '{ a: string; b: string; c: string; }' is not assignable to type 'U'.
//                                    Object literal may only specify known properties, and 'c' does not exist in type 'U'.
//                                    (2322)

问题归结起来就是TypeScript无法保证您试图解构的属性确实存在:

type A = { a: string }
type B = { b: string }

type U = A | B

// This is allowed, since all properties are known
const foo: U = { a: "foo", b: "bar" }

// These fail, since the type information cannot guarantee the `a` or `b` properties exist on U:
// By the definition of U, `foo` is guaranteed to have at least all the properties of either `A` or `B`,
// but if `foo` extends `B`, it's not guaranteed to have `a` and vice versa
const { a } = foo;
const { b } = foo;

但是当类型系统可以保证一个属性存在于联合体的所有部分时,您就可以安全地解构该属性:

type A = { a: string, x: number }
type B = { b: string, x: number }

type U = A | B
const foo: U = { a: "foo", b: "bar", x: 0 }

// These fail as per above
const { a } = foo;
const { b } = foo;

// This will work, as `x` is guaranteed to exist on all values of type `U`
const { x } = foo;

现在来看一下您的确切问题,TypeScript会告诉您问题是什么,尽管有点过于冗长:
TS 2339:类型“PropsWithChildren”上不存在属性“to”({ onClick:鼠标事件处理程序;}|{到:字符串;})& {按钮组件?:({单击:鼠标事件处理程序}:{ onClick:任意;})=〉元素;} & BoxProps〉'中找到的。
由于StickyButton是一个FC<Props>,根据FC<Props>的定义,它是一个接受Props类型参数的函数。在您的示例中,类型Props等于错误消息中“does not exist on type”后面的单词salad。并且TypeScript无法保证名为to的属性将始终存在于其中。因此,它不能在函数参数中被反结构化:

const StickyButton: React.FC<…> = ({
  children,
  onClick,
  to, // This might not exist
  buttonComponent: ButtonComponent = Button,
  ...props
}) => { … }

相关问题