Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript. How to use not exported type definitions?

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

like image 830
Nazik Orl Avatar asked Oct 15 '17 12:10

Nazik Orl


3 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.

like image 185
Aleksey L. Avatar answered Oct 12 '22 10:10

Aleksey L.


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

like image 44
Nazik Orl Avatar answered Oct 12 '22 09:10

Nazik Orl


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;
    }
}
like image 2
Bruno Grieder Avatar answered Oct 12 '22 10:10

Bruno Grieder