5
votes

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

4

4 Answers

4
votes

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

3
votes

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.

2
votes

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;
    }
}
0
votes

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