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
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.
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
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;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With