【问题标题】:Why is type SFC<AnchorProps> not assignable to type SFC<{}>?为什么类型 SFC<AnchorProps> 不能分配给类型 SFC<{}>?
【发布时间】:2020-02-25 06:12:40
【问题描述】:

以下代码在使用 TypeScript 编译时会抛出错误。

import React, { SFC } from "react"
import { Link } from "react-router-dom"
import { HashLink } from "react-router-hash-link"
import MarkdownToJSX from "markdown-to-jsx"

interface AnchorProps {
  baseUrl: string
  relativeUrl: string
  href: string
}

const Anchor: SFC<AnchorProps> = function(props) {
  /*
  FYI, props.baseUrl and props.relativeUrl is used by code I didn’t include in
  this example to keep things simple.
  */
  if (
    props.href.match(/^http(s)?:\/\//) ||
    props.href.match(/^mailto:/) ||
    props.href.match(/^tel:/)
  ) {
    return (
      <a href={props.href} rel="noopener noreferrer" target="_blank">
        {props.children}
      </a>
    )
  } else if (props.href.match(/^#/)) {
    return (
      <HashLink to={props.href} smooth>
        {props.children}
      </HashLink>
    )
  } else {
    return <Link to={props.href}>{props.children}</Link>
  }
}

const Markdown = function() {
  return (
    <MarkdownToJSX
      options={{
        overrides: {
          a: {
            component: Anchor,
            props: {
              baseUrl: "/privacy-guides",
              relativeUrl: "",
            },
          },
        },
      }}
    >
      # This is [markdown](markdown)
    </MarkdownToJSX>
  )
}

export default Markdown
/Users/sunknudsen/Sites/sunknudsen/sunknudsen-website/src/Test.tsx
TypeScript error in /Users/sunknudsen/Sites/sunknudsen/sunknudsen-website/src/Test.tsx(40,13):
No overload matches this call.
  Overload 1 of 2, '(props: Readonly<MarkdownProps>): Markdown', gave the following error.
    Type 'FunctionComponent<AnchorProps>' is not assignable to type 'string | SFC<{}> | ComponentClass<{}, any>'.
      Type 'FunctionComponent<AnchorProps>' is not assignable to type 'SFC<{}>'.
        Types of parameters 'props' and 'props' are incompatible.
          Type '{ children?: ReactNode; }' is not assignable to type 'PropsWithChildren<AnchorProps>'.
            Type '{ children?: ReactNode; }' is missing the following properties from type 'AnchorProps': baseUrl, relativeUrl, href
  Overload 2 of 2, '(props: MarkdownProps, context?: any): Markdown', gave the following error.
    Type 'FunctionComponent<AnchorProps>' is not assignable to type 'string | SFC<{}> | ComponentClass<{}, any>'.
      Type 'FunctionComponent<AnchorProps>' is not assignable to type 'SFC<{}>'.  TS2769

    38 |         overrides: {
    39 |           a: {
  > 40 |             component: Anchor,
       |             ^
    41 |             props: {
    42 |               baseUrl: "/privacy-guides",
    43 |               relativeUrl: "",

我不明白为什么。

【问题讨论】:

  • 这个问题是特定于 React 等特定库的吗?如果是这样,那么它可能应该被标记为这样。如果没有,那么请考虑将上述代码编辑为minimal reproducible example,可以将其放入独立的 IDE 以演示问题,并且仅演示您遇到的问题。现在缺少一些类型定义(ReactNode 等)。
  • 我猜这是因为FunctionComponent&lt;P&gt; 是一种函数类型,其中P 出现在逆变位置,因此它不是协变的,即FunctionComponent&lt;B&gt; 不是FunctionComponent&lt;A&gt; 的子类型当BA 的子类型时。但是我不知道这段代码中使用的 React 类型,所以我不知道这是否是一个完整的答案。
  • 那一行是call signature。您可以将(a: A) =&gt; B 视为{ (a: A): B } 的简写形式;后一种形式允许您添加其他属性。
  • 一个 React 函数式组件只是一个接受 props 并返回一个 react 元素的函数。函数参数的行为是逆变的..所以你得到了错误,但我相信 jcalz 很快就会回答它:)
  • @sunknudsen 创建一个示例,当我将该示例粘贴到本地文本编辑器中时,将显示错误。如果该示例需要我在本地安装 @types/react 也没关系。照原样,当我将您的示例粘贴到本地文本编辑器中时,没有错误。一个很好的例子是不超过 20 行代码。

标签: reactjs typescript


【解决方案1】:

这似乎是markdown-to-jsx 类型定义的限制。

具体来说,MarkdownToJSX.options.overrides 属于ComponentOverride 类型,ComponentOverride 属于这种类型:

export type ComponentOverride = string | React.ComponentClass | React.SFC | {
    component: string | React.ComponentClass | React.SFC;
    props?: any;
};

该类型不允许您将泛型参数传递给React.SFC,这意味着它采用其默认泛型参数:

type SFC<P = {}> = FunctionComponent<P>;

默认的泛型参数是一个空对象{}

由于您的代码试图将 AnchorProps 类型分配给空对象类型,因此编译器会抱怨。

这在运行时是否会成为问题取决于底层 JavaScript 在运行时的工作方式。 可能可以这样投射:

const Markdown = function() {
  return (
    <MarkdownToJSX
      options={{
        overrides: {
          a: {
            component: Anchor,
            props: {
              baseUrl: "/privacy-guides",
              relativeUrl: ""
            }
            // We have tested that this works at runtime,
            // we we know more than the compiler knows.
          } as ComponentOverride
        }
      }}
    >
      # This is [markdown](markdown)
    </MarkdownToJSX>
  );
};

【讨论】:

  • 修复类型定义有多容易?见github.com/DefinitelyTyped/DefinitelyTyped/pull/…
  • 我很确定我的any 建议非常幼稚。
  • 现在投射会更容易。类型定义绝对是可修复的,但这样做需要时间和知识。更新类型以使其具有通用性需要了解底层 JavaScript 以确保类型反映运行时发生的情况。
  • 我确认了选角工作......但这有点小题大做吧?
  • @sunknudsen 这有点像 hack,但只是 bit 的 hack。 npm 包具有不完整的类型定义是相当普遍的。实用的解决方法是在您知道的比编译器知道的多时强制转换(带注释)。当您有时间/知识更新类型定义时,请先在本地执行此操作,然后提交拉取请求。与使用@ts-ignore 相比,铸造肯定不是一种黑客攻击。演员表必须至少是合理的(例如,如果没有额外的说服,转换为 Date 是行不通的。)
猜你喜欢
  • 1970-01-01
  • 2019-06-24
  • 2021-02-07
  • 2022-11-14
  • 2021-07-02
  • 1970-01-01
  • 2022-01-27
  • 2016-07-31
  • 1970-01-01
相关资源
最近更新 更多