typescripttypesconstantstypescript-typingscustom-type

Why cannot I use value in constant as custom type definition?


This is not compilable

export const QuantityModes = {
    POSITIVE: 'POSITIVE', 
    NEGATIVE: 'NEGATIVE',
    ANY: 'ANY', 
};

export type QuantityMode = QuantityModes.POSITIVE | QuantityModes.NEGATIVE | QuantityModes.ANY;

This is compilable

export const QuantityModes = {
    POSITIVE: 'POSITIVE',
    NEGATIVE: 'NEGATIVE',
    ANY: 'ANY',
};

export type QuantityMode = 'POSITIVE' | 'NEGATIVE' | 'ANY';

Solution

  • The first problem you have is that the type of the properties are actually all string not string literal types like you might expect. To fix this we can either use a type assertion:

    export const QuantityModes = {
        POSITIVE: 'POSITIVE' as 'POSITIVE', 
        NEGATIVE: 'NEGATIVE' as 'NEGATIVE',
        ANY: 'ANY' as 'ANY', 
    };
    

    Use a helper function to hint to the compiler we want type literals:

    export const QuantityModes = (<T extends { [P in keyof T]: P }>(o: T)=> o)({
        POSITIVE: 'POSITIVE', 
        NEGATIVE: 'NEGATIVE',
        ANY: 'ANY', 
    })
    

    Or, starting in 3.4 (unrelease yet), you can write as const:

    export const QuantityModes = {
        POSITIVE: 'POSITIVE', 
        NEGATIVE: 'NEGATIVE',
        ANY: 'ANY', 
    } as const
    

    You can type a type relative to another type but the syntax is different. Firstly if you want to access the type of a property the syntax is type['propName'] (also called an index type query). But you want to access the type of a constant, to do that you need to use typeof const. So you could write:

    export type QuantityMode = typeof QuantityModes["POSITIVE"] | typeof QuantityModes["NEGATIVE"] | typeof QuantityModes["ANY"];
    

    You can also use union to simplify a bit with the same result:

    export type QuantityMode = typeof QuantityModes["POSITIVE" | "NEGATIVE" | "ANY"];
    

    If the union contains all property names, then we can just use keyof type to get a union of all property names within a type (ensuring all future additions are also automatically added to the type)

    export type QuantityMode = typeof QuantityModes[keyof typeof QuantityModes];
    

    Since in this case the property name and the property type are the same we could even just use keyof:

    export type QuantityMode = keyof typeof QuantityModes;