简体   繁体   English

Typescript - 从 object 属性值推断泛型类型

[英]Typescript - infer generic type from object prop value

I read a lot, and found similar things but not exactly what I want.我读了很多,发现了类似的东西,但不是我想要的。

I want to define a type by using a prop value inside an object and use that information to define other property inside this object.我想通过使用 object 中的道具值来定义类型,并使用该信息来定义此 object 中的其他属性。

JSX.IntrinsicElements includes definitions for all react elements. JSX.IntrinsicElements 包括所有反应元素的定义。 let's focus on the SVG elements circle and path for this example.让我们关注此示例的 SVG 元素circlepath

so lets try define the type defention:所以让我们尝试定义类型防御:

export type svgCustomType<T extends "path" | "circle"> = {
  svgElem: T;
  svgProps?: { [key in keyof JSX.IntrinsicElements[T]]: JSX.IntrinsicElements[T] };
};

here I try to get the type value T(which can be "path" or "circle") and use this value to define the appropriate properties type for svgProps .(can be React.SVGProps<SVGCircleElement> or React.SVGProps<SVGPathElement>在这里,我尝试获取类型值 T(可以是“路径”或“圆”)并使用该值来定义svgProps的适当属性类型。(可以是React.SVGProps<SVGCircleElement>React.SVGProps<SVGPathElement>

example usage:示例用法:

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`
  },
};

Why do I get Generic type 'svgCustomType' requires 1 type argument(s) ?为什么我会得到Generic type 'svgCustomType' requires 1 type argument(s) I want to get this type from svgElem value.我想从svgElem值中获取这种类型。

i will just mention that using const test: svgCustomType<"path"> = {...} will work and cx will not be accepted.我只想提一下,使用const test: svgCustomType<"path"> = {...}将起作用并且cx将不会被接受。

for clearly :明确
I'm not sure if is even possible in typescript.我不确定在 typescript 中是否有可能。
I'm writing a react lib and i want my user to be able to get IntelliSense suggestions while typing,and if the user running typescript(and not javascript) he will also will get type errors.我正在编写一个反应库,我希望我的用户能够在输入时获得 IntelliSense 建议,如果用户运行 typescript(而不是 javascript),他也会收到类型错误。
let's say my lib component have prop svgHeadProp with type svgCustomType .假设我的 lib 组件具有类型为svgCustomType svgHeadProp
so i want the user to be able to write:所以我希望用户能够写:

svgHeadProp={svgElem:"path",svgProps:{...}}

and not:并不是:

svgHeadProp<"path">={svgElem:"path",svgProps:{...}}

because not all my users uses typescript(and for the ones that does, its annoying specifying type for a prop )因为不是我所有的用户都使用打字稿(对于那些使用打字稿的人,它烦人的为 prop 指定类型)

The issue is that to use SvgCustomTypeGeneric in a type signature, you'll have to pass that type parameter.问题是要在类型签名中使用SvgCustomTypeGeneric ,您必须传递该类型参数。 Using SvgCustomTypeGeneric<'path' | 'circle'>使用SvgCustomTypeGeneric<'path' | 'circle'> SvgCustomTypeGeneric<'path' | 'circle'> won't work, as this will just allow every occurrence of T to be 'path' or 'circle' without any dependency between them. SvgCustomTypeGeneric<'path' | 'circle'>不起作用,因为这只会允许每次出现的T都是'path''circle'而它们之间没有任何依赖关系。 What will work though is a union type, SvgCustomTypeGeneric<'path'> | SvgCustomTypeGeneric<'circle'>不过可行的是联合类型SvgCustomTypeGeneric<'path'> | SvgCustomTypeGeneric<'circle'> SvgCustomTypeGeneric<'path'> | SvgCustomTypeGeneric<'circle'> , which can be created from the 'path' | 'circle' SvgCustomTypeGeneric<'path'> | SvgCustomTypeGeneric<'circle'> ,可以从'path' | 'circle'创建'path' | 'circle' union. 'path' | 'circle'工会。

To show how it works, let's rename the generic function to SvgCustomTypeGeneric , and use JSX.IntrinsicElements[T] as the type of svgProps (otherwise the values are nested, eg svgProps: cx: {cx: 10}) ):为了展示它是如何工作的,让我们将通用 function 重命名为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]
}

To create a union type of SvgCustomTypeGeneric types, you can use a mapped type to create an object with for each key the corresponding SvgCustomTypeGeneric type, and extract the values from this:要创建SvgCustomTypeGeneric类型的联合类型,您可以使用映射类型创建 object,每个键对应的SvgCustomTypeGeneric类型,并从中提取值:

type SvgCustomType = {[K in SvgKey]: SvgCustomTypeGeneric<K>}[SvgKey]
//  evaluates to: SvgCustomTypeGeneric<"path"> | SvgCustomTypeGeneric<"circle">

When testing it, the cx property is not helpful as it is also allowed on an SVGPathElement , but the ref property requires a correctly typed value, and can be used instead:在测试它时, 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>(),
    },
  },
]

An alternative solution is to create a generic constructor function to validate the objects.另一种解决方案是创建一个通用构造函数 function 来验证对象。 It's a bit simpler and gives prettier error messages, but does require adding actual code rather than just types:它更简单一些,并提供更漂亮的错误消息,但确实需要添加实际代码而不仅仅是类型:

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> 
  },
})

TypeScript playground TypeScript操场

UPDATE: It's also possible to write SvgCustomType as a one-liner, by using conditional types to introduce the type variable and distribute it over the union:更新:也可以将SvgCustomType编写为单线,通过使用条件类型来引入类型变量并将其分布在联合上:

export type SvgCustomTypeOneLiner =
  SvgKey extends infer T ? T extends SvgKey ? {
    svgElem: T;
    svgProps?: JSX.IntrinsicElements[T]
  } : never : never

And if you really want to go all out, you can even drop the dependency on SvgKey :如果你真的想要 go 全力以赴,你甚至可以放弃对SvgKey的依赖:

type SvgCustomTypeUltimate = 
  keyof JSX.IntrinsicElements extends infer T ? T extends keyof JSX.IntrinsicElements ? {
    svgElem: T;
    svgProps?: JSX.IntrinsicElements[T]
  } : never : never

Both these types have the same behavior as SvgCustomType defined above.这两种类型都具有与上面定义的SvgCustomType相同的行为。

TypeScript playground TypeScript操场

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM