Reputation: 151
Just look at this typescript code:
lib.ts
interface Human {
name: string;
age: number;
}
export default class HumanFactory {
getHuman(): Human {
return {
name: "John",
age: 22,
}
}
}
index.ts
import HumanFactory from "./lib";
export class Foo {
human: any;
constructor() {
const factory = new HumanFactory();
this.human = factory.getHuman();
}
diffWithError(age: number): number {
return age - this.human.name;
}
diffWithTypingAndAutocoplete(age: number): number {
const factory = new HumanFactory();
return age - factory.getHuman().name;
}
}
The problem in "human" property of "Foo" class. I can't define type of this variable as "Human" interface from lib.ts.
In method "diffWithError" I make an error - use number "age" and string "name" in arithmetic operation, but neither IDE nor ts compiler know about this, because in this context, type of "this.human.name" is "any"
In method "diffWithTypingAndAutocoplete" I just use method "getHuman". IDE and compiler know about type of method result. This is "Human" interface and field "name" are "string". This method trigger an error when compiling sources.
I found this problem when I tried import .d.ts file of JS lib and I don't have ability to export needed interface. Can I somehow define valid type of "human" property without copy and paste code of "Human" interface whenever i want to define type (and without inline type definitions, like { name: string, age: number }).
I don not want to create instances of not exported classes, I just want type checking and autocomplete.
P.S. I try write this:
human: Human
compiler trigger an error: "error TS2304: Cannot find name 'Human'" (expected behavior)
P.S.S I try to do this with triple slash directive:
///<reference path="./lib.ts" />
but this not working too.
Sorry for my poor english and thanks for answers
Upvotes: 10
Views: 9665
Reputation: 37918
Update
Now with conditional types in place it can be done without workarounds:
type Human = ReturnType<HumanFactory['getHuman']>
Workaround for TS < 2.8
If you can't change lib.ts you can "query" return type of getHuman
function. It is a bit tricky because typescript currently doesn't provide any straight forward method for this:
import HumanFactory from "./lib";
const dummyHuman = !true && new HumanFactory().getHuman();
type Human = typeof dummyHuman;
export class Foo {
human: Human;
// ...
}
!true &&
is used to prevent new HumanFactory().getHuman()
execution.
Upvotes: 5
Reputation: 1560
This was made easier with the introduction of the ReturnType<>
static type in TypeScript 2.8.
import HumanFactory from "./lib";
type Human = ReturnType<typeof HumanFactory.prototype.getHuman>
See also https://stackoverflow.com/a/43053162
Upvotes: 0
Reputation: 29814
You need to export Human
so that it is visible - and usable - from index.ts
as well (as HumanFactory
). Do not use default exports but "named exports" i.e. try this
export interface Human {
name: string;
age: number;
}
export class HumanFactory {
getHuman(): Human {
return {
name: "John",
age: 22,
}
}
}
In index.ts
import { Human, HumanFactory} from "./lib";
** EDIT **
If you cannot change lib.d.ts
then redefine Human
and use double-casting i.e.
import HumanFactory from "./lib";
interface Human {
name: string;
age: number;
}
export class Foo {
human: Human; // <= change here
constructor() {
const factory = new HumanFactory();
this.human = factory.getHuman() as any as Human; // <= double casting
}
diffWithError(age: number): number {
return age - this.human.name;
}
diffWithTypingAndAutocoplete(age: number): number {
const factory = new HumanFactory();
return age - factory.getHuman().name;
}
}
Upvotes: 1
Reputation: 151
I found a solution!
I make file human-interface.ts with this content:
import HumanFactory from './lib';
const humanObject = new HumanFactory().getHuman();
type HumanType = typeof humanObject;
export default interface Human extends HumanType {}
Import of this interface in main file not execute creation of "HumanFactory" and type checking work properly.
Thanks for idea with typeof
Upvotes: 4