问题
In typescript, I can declare a generic function like so:
const fn: <T>(arg: T)=>Partial<T>
In this case, TypeScript can sometimes infer the type parameter of the function based on the actual parameters I pass it. Is there a similar way to define a generic object literal whose type parameter can be dynamically inferred based on its contents? Something like:
interface XYZ {
obj: <T>{ arr: T[], dict: Partial<T> }
}
I am aware I can make the entire interface generic like so:
interface XYZ<T> {
arr: T[],
dict: Partial<T>
}
but I want to avoid that, because then I would have to declare the generic type in advance whenever I am using the interface. For example
const x: XYZ
will not work. If I want to make the declaration general, I am forced to write:
const x: XYZ<any>
but this does not allow TypeScript to dynamically infer the specific generic type based on the actual contents of x
回答1:
Ah, you want generic values as discussed in Microsoft/TypeScript#17574. As you note, they don't exist in the language except in the case of generic functions. You can go give a 👍 to that issue if you want, or discuss your use case if you think it's helpful.
Given the generic interface
interface XYZ<T> {
arr: T[],
dict: Partial<T>
}
I would just use this workaround: Make a generic function to verify that a value is XYZ<T> for some T, and allow type inference to actually infer T whenever it is necessary. Never try to declare something of type XYZ. Like this:
const asXYZ = <T>(xyz: XYZ<T>) => xyz;
const x = asXYZ({
arr: [{ a: 1, b: 2 }, { a: 3, b: 4 }],
dict: { a: 1300 }
}); // becomes XYZ<{a: number, b: number}>
The above usually works for me in practice. The pro is that it's "natural" TypeScript. The con is it doesn't represent the "I don't care what type T is" properly.
If you really want, you could define an existential type. TypeScript doesn't natively support these, but there is a way to represent it:
interface SomeXYZ {
<R>(processXYZ: <T>(x: XYZ<T>) => R): R
}
const asSomeXYZ = <T>(xyz: XYZ<T>): SomeXYZ =>
<R>(processXYZ: <T>(x: XYZ<T>) => R) => processXYZ(xyz);
The SomeXYZ type is a concrete type that doesn't care anymore about T, but holds a reference to XYZ<T> for some T. You use asSomeXYZ to create one from an object:
const someXYZ: SomeXYZ = asSomeXYZ({
arr: [{ a: 1, b: 2 }, { a: 3, b: 4 }],
dict: { a: 1300 }
}); // SomeXYZ
And you use it by passing a function that processes the held reference. That function has to be ready for XYZ<T> for any T, since you don't know what type of T a SomeXYZ is holding.
// use one
const xyzArrLength = someXYZ((xyz => xyz.arr.length))
The xyzArrLength is a number, since the function xyz => xyz.arr.length returns a number no matter what T is.
Existential types in TypeScript are awkward, since there's a lot of inversion of control going on. That's the major downside to this, and why I usually go with the less-perfect-but-easier-to-think-about workaround I presented first.
Hope that helps. Good luck!
EDIT: re-reading your question makes me think you’re actually asking for the answer I listed as a “workaround”. So, uh... use that? Cheers.
回答2:
interface MyGenericObjectLiteral<T> {
arr: T[],
dict: Partial<T>
}
interface XYZ {
obj: MyGenericObjectLiteral<any>
}
Interface XYZ here will not be generic, just the subobject MyGenericObjectLiteral. It is actually just what you desired, except that it has a bit different syntax.
来源:https://stackoverflow.com/questions/50509952/dynamic-generic-type-inference-for-object-literals-in-typescript