admin管理员组文章数量:1125727
class Token<T> {
public name: T;
constructor(name: T) {
this.name = name;
}
}
class LazyToken<T> {
public callback: () => GenericToken<T>;
constructor(callback: () => GenericToken<T>) {
this.callback = callback;
}
public resolve() {
return this.callback();
}
}
type GenericToken<T = unknown> = Token<T> | LazyToken<T> | Newable<T>;
type Newable<
TInstance = unknown,
TArgs extends unknown[] = any[]
> = new (...args: TArgs) => TInstance;
function get<T>(token: GenericToken<T>, options: any = {}): T | undefined {
return 123 as T;
}
class Test1 {
}
class Test2 {
public name = 123;
}
class Test3 {
sayHello() { }
}
class Test4 {
public hello = 123;
}
class Test5 {
public hello = 123;
sayHello() { }
}
const test1 = get(Test1); // Test1 | undefined
const test2 = get(Test2); // test2: string | undefined
const test3 = get(Test3); // test3: string | undefined
const test4 = get(Test4); // test4: string | undefined
const test5 = get(Test5); // test5: string | undefined
Playground
The type of test1
is Test1 | undefined
as I expect. But test2
through test5
are all of type string | undefined
, instead of Test2 | undefined
through Test5 | undefined
, which I don't expect. How can I fix it?
class Token<T> {
public name: T;
constructor(name: T) {
this.name = name;
}
}
class LazyToken<T> {
public callback: () => GenericToken<T>;
constructor(callback: () => GenericToken<T>) {
this.callback = callback;
}
public resolve() {
return this.callback();
}
}
type GenericToken<T = unknown> = Token<T> | LazyToken<T> | Newable<T>;
type Newable<
TInstance = unknown,
TArgs extends unknown[] = any[]
> = new (...args: TArgs) => TInstance;
function get<T>(token: GenericToken<T>, options: any = {}): T | undefined {
return 123 as T;
}
class Test1 {
}
class Test2 {
public name = 123;
}
class Test3 {
sayHello() { }
}
class Test4 {
public hello = 123;
}
class Test5 {
public hello = 123;
sayHello() { }
}
const test1 = get(Test1); // Test1 | undefined
const test2 = get(Test2); // test2: string | undefined
const test3 = get(Test3); // test3: string | undefined
const test4 = get(Test4); // test4: string | undefined
const test5 = get(Test5); // test5: string | undefined
Playground
The type of test1
is Test1 | undefined
as I expect. But test2
through test5
are all of type string | undefined
, instead of Test2 | undefined
through Test5 | undefined
, which I don't expect. How can I fix it?
1 Answer
Reset to default 2The problem is that there is an unexpected ambiguity in your types. The type Token<string>
corresponding to an instance of the Token
class where T
is string
is structurally equivalent to {name: string}
. And this turns out to be a supertype of the Function
interface, which (at least for ES2015 and later) is declared in TypeScript's library to have a name
property of type string
. So, as far as TypeScript is concerned, everything with a string
-valued name
property is also a Token<string>
. So that includes all Function
s, which includes all class constructors, like typeof Test1
, typeof Test2
, et cetera. Yes, that's weird, but it's a consequence of TypeScript's structural type system. If you're used to nominally typed languages like Java or C# where such things simply cannot happen, then you might object that of course a class constructor is not an instance of Token
. But TypeScript is only looking at the shape of the type, not where it is declared.
So when you try to infer T
from a value of type GenericToken<T>
which is a class constructor, then it is ambiguous. It could either decide that the GenericToken<T>
is a Newable<T>
and infer T
as the instance type of the class constructor, or it could decide that the GenericToken<T>
is a Token<string>
and infer T
as string
. The particular details of when TypeScript chooses one over the other might be interesting, but I don't think you can rely on them. It's best to avoid the problem, rather than try to really understand why typeof Test1
is inferred as a Newable<Test1>
while the rest of them are inferred as a Token<string>
. It's ambiguous, and TypeScript chooses one, and it's not always what you want. (See this FAQ entry for a similar issue with inference.)
The way to fix the problem is to break the structural compatibility between Function
and Token<string>
. I'd do this by adding any property to Token
that you don't expect to see in the wild. (You could also add a private
property to Token
, since that will never conflict.) For example:
class Token<T> {
anyOtherProp = true; // <-- this could be just about anything
public name: T;
constructor(name: T) {
this.name = name;
}
}
Now there's no ambiguity. A class constructor is very unlikely to be a Token<string>
(it would need to have a static
anyOtherProp
property of type boolean
), so you get the inference you expect in all the cases:
const test1 = get(Test1); // Test1 | undefined
const test2 = get(Test2); // Test2 | undefined
const test3 = get(Test3); // Test3 | undefined
const test4 = get(Test4); // Test4 | undefined
const test5 = get(Test5); // Test5 | undefined
Playground link to code
本文标签: typescriptWhy are generics not working properly hereStack Overflow
版权声明:本文标题:typescript - Why are generics not working properly here? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1736641609a1946006.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
const token = new Token<TestClass>('a test token'); const testInst = get(token);
– threetree Commented 2 days agoclass Token<T>{public name:string; constructor(name:string){this.name=name;}}
But the ide will complain that the T type is not used in the implementation of Token. Can you tell me how to implement Token in the best practice? – threetree Commented 2 days ago