Typescript via jsdocs

Steven Chetwynd

javascript

development

350 Words … ⏲ Reading Time: 1 Minute, 35 Seconds

2025-02-28 13:22 +0000


Generic Classes

To create a generic class we can use the @template tag above the class, and reuse the template name in the class methods and properties:

/**
 * @template T
 */
class MyClass {
    /** @type {t} */
    #prop;

    /**
     * @param {T} value
     */
    constructor(value) {
        this.#prop = value;
    }

    /**
     * @returns {T}
     */
    getValue() {
       return this.#prop;
    }
}

/** @type {MyClass<string>} */
const foo = new MyClass("test");

If the generic needs to extend another type we can use @template {OtherType} T.

Classes implement

To make a class implement an interface, I like to declare the interface in a .d.ts file and import it into my file. But it is not possible to use import directly in the @implements tag, we need to use a @typedef first.

// types.d.ts

export interface MyInterface {
    greet: () => string;
}
/**
 * @typedef {import("./types").MyInterface
 * @implements {MyInterface}
 */
class EsperantoGreeter {
    /**
     * @returns {string}
     */
    greet() {
        return "Saluton";
    }
}

Overloading functions

It is possible to use the @overload tag to list a series of different inputs to a single function/method and the results of the each different input.

I recently found this useful when passing an object or array of objects into a function which would return either an object or an array of objects depending on what was passed.

export interface my_obj {
    my_value: string;
    my_other_value: string;
}

export interface MyObj {
    myValue: string;
    myOtherValue: string;
}

/**
 * @overload
 * @param {import("./types").my_obj} obj
 * @returns {import("./types").MyObj}
 */
/**
 * @overload
 * @param {import("./types").my_obj[]} obj
 * @returns {import("./types").MyObj[]}
 */
/**
 * @param {import("./types").my_obj | import("./types").my_obj[]} obj
 * @returns {import("./types").MyObj | import("./types").MyObj[]}
 */
function myFunction(obj) {
    const transformObj = (obj) => ({
        myValue: obj.my_value,
        myOtherValue: obj.my_other_value
    });

    if (Array.isArray(obj)) {
        return obj.map((el) => transformObj(el));
    }

    return transformObj(obj);
}

I have only come across this whilst introducing types to old code, and going forward I would choose not to write code like this because it can cause the JavaScript engine to bail out on any optimisation it has made. (A crash course in just-in-time (JIT) compilers)