Typescript - 从对象属性值推断泛型类型

Eli*_*ski 6 typescript reactjs

我读了很多书,发现了类似的东西,但不完全是我想要的。

我想通过使用对象内的 prop 值来定义类型,并使用该信息来定义该对象内的其他属性。

JSX.IntrinsicElements 包含所有 React 元素的定义。让我们重点关注 SVG 元素circle以及path本示例。

所以让我们尝试定义类型防御:

export type svgCustomType<T extends "path" | "circle"> = {
  svgElem: T;
  svgProps?: { [key in keyof JSX.IntrinsicElements[T]]: JSX.IntrinsicElements[T] };
};
Run Code Online (Sandbox Code Playgroud)

在这里,我尝试获取类型值 T(可以是“路径”或“圆”)并使用该值来定义svgProps.(可以是React.SVGProps<SVGCircleElement>React.SVGProps<SVGPathElement>

用法示例:

const test: svgCustomType = {  //Error: "Generic type 'svgCustomType' requires 1 type argument(s)", why?
  svgElem: "path",
  svgProps: {
    cx: 10,  // next step: should be error here because cx is not defined in `SVGPathElement`
  },
};
Run Code Online (Sandbox Code Playgroud)

为什么我得到Generic type 'svgCustomType' requires 1 type argument(s)?我想从svgElem值中获取这种类型。

我只是提到使用const test: svgCustomType<"path"> = {...}会起作用并且cx不会被接受。

明确地说
我不确定在打字稿中是否可能。
我正在编写一个反应库,我希望我的用户能够在打字时获得 IntelliSense 建议,如果用户运行打字稿(而不是 javascript),他也会收到类型错误。
假设我的 lib 组件有svgHeadProptype 的prop svgCustomType
所以我希望用户能够写:

svgHeadProp={svgElem:"path",svgProps:{...}}
Run Code Online (Sandbox Code Playgroud)

并不是:

svgHeadProp<"path">={svgElem:"path",svgProps:{...}}
Run Code Online (Sandbox Code Playgroud)

因为并非所有用户都使用 typescript(对于那些使用 typescript 的用户,为 prop 指定类型很烦人)

Obl*_*sys 15

问题是要SvgCustomTypeGeneric在类型签名中使用,您必须传递该类型参数。使用SvgCustomTypeGeneric<'path' | 'circle'>是行不通的,因为这只会允许它们之间T存在'path'或不存在任何依赖关系。'circle'不过,起作用的是联合类型 ,SvgCustomTypeGeneric<'path'> | SvgCustomTypeGeneric<'circle'>它可以从联合创建'path' | 'circle'

为了展示它是如何工作的,让我们将泛型函数重命名为SvgCustomTypeGeneric,并用作JSX.IntrinsicElements[T]的类型svgProps(否则值是嵌套的,例如svgProps: cx: {cx: 10})):

type SvgKey = 'path' | 'circle'

export type SvgCustomTypeGeneric<T extends SvgKey> = {
  svgElem: T;
  svgProps?: JSX.IntrinsicElements[T]
}
Run Code Online (Sandbox Code Playgroud)

要创建类型的联合类型SvgCustomTypeGeneric,您可以使用映射类型来创建一个对象,其中每个键都有相应的SvgCustomTypeGeneric类型,并从中提取值:

type SvgCustomType = {[K in SvgKey]: SvgCustomTypeGeneric<K>}[SvgKey]
//  evaluates to: SvgCustomTypeGeneric<"path"> | SvgCustomTypeGeneric<"circle">
Run Code Online (Sandbox Code Playgroud)

测试时,该cx属性没有帮助,因为它也可以在 上使用SVGPathElement,但该ref属性需要正确键入的值,并且可以改为使用:

const test: SvgCustomType[] = [{
    svgElem: 'path',
    svgProps: {
      cx: 10, // No error, SVGPathElement can have 'cx' properties.
      ref: createRef<SVGPathElement>(),
    },
  }, {
    svgElem: 'circle',
    svgProps: {
      cx: 10,
      ref: createRef<SVGCircleElement>(),
    },
  }, { // ERROR
    svgElem: 'circle',
    svgProps: {
      cx: 10,
      ref: createRef<SVGPathElement>(),
    },
  },
]
Run Code Online (Sandbox Code Playgroud)

另一种解决方案是创建通用构造函数来验证对象。它更简单一些,并提供更漂亮的错误消息,但确实需要添加实际代码而不仅仅是类型:

const mkSvg =<K extends SvgKey>(x: SvgCustomTypeGeneric<K>) => x

const test = mkSvg({
  svgElem: 'circle',
  svgProps: {
    ref: createRef<SVGPathElement>(),
    // Type 'RefObject<SVGPathElement>' is not assignable to type 'LegacyRef<SVGCircleElement> 
  },
})
Run Code Online (Sandbox Code Playgroud)

TypeScript 游乐场

更新:也可以SvgCustomType通过使用条件类型来引入类型变量并将其分布在联合上,将其编写为单行代码:

export type SvgCustomTypeOneLiner =
  SvgKey extends infer T ? T extends SvgKey ? {
    svgElem: T;
    svgProps?: JSX.IntrinsicElements[T]
  } : never : never
Run Code Online (Sandbox Code Playgroud)

如果你真的想全力以赴,你甚至可以放弃对以下内容的依赖SvgKey

type SvgCustomTypeUltimate = 
  keyof JSX.IntrinsicElements extends infer T ? T extends keyof JSX.IntrinsicElements ? {
    svgElem: T;
    svgProps?: JSX.IntrinsicElements[T]
  } : never : never
Run Code Online (Sandbox Code Playgroud)

这两种类型都具有与上面定义的相同的行为SvgCustomType

TypeScript 游乐场

  • 这就像魔法(以一种好的方式) (2认同)