Is there a way to create nominal types in TypeScript that extend primitive types?

前端 未结 5 631
悲&欢浪女
悲&欢浪女 2020-12-01 14:39

Say I have two types of numbers that I\'m tracking like latitude and longitude. I would like to represent these variables with the basic num

5条回答
  •  情深已故
    2020-12-01 14:52

    Here is a simple way to achieve this:

    Requirements

    You only need two functions, one that converts a number to a number type and one for the reverse process. Here are the two functions:

    module NumberType {
        /**
         * Use this function to convert to a number type from a number primitive.
         * @param n a number primitive
         * @returns a number type that represents the number primitive
         */
        export function to(n : number) : T {
            return ( n);
        }
    
        /**
         * Use this function to convert a number type back to a number primitive.
         * @param nt a number type
         * @returns the number primitive that is represented by the number type
         */
        export function from(nt : T) : number {
            return ( nt);
        }
    }
    

    Usage

    You can create your own number type like so:

    interface LatitudeNumber extends Number {
        // some property to structurally differentiate MyIdentifier
        // from other number types is needed due to typescript's structural
        // typing. Since this is an interface I suggest you reuse the name
        // of the interface, like so:
        LatitudeNumber;
    }
    

    Here is an example of how LatitudeNumber can be used

    function doArithmeticAndLog(lat : LatitudeNumber) {
        console.log(NumberType.from(lat) * 2);
    }
    
    doArithmeticAndLog(NumberType.to(100));
    

    This will log 200 to the console.

    As you'd expect, this function can not be called with number primitives nor other number types:

    interface LongitudeNumber extends Number {
        LongitudeNumber;
    }
    
    doArithmeticAndLog(2); // compile error: (number != LongitudeNumber)
    doArithmeticAndLog(NumberType.to(2)); // compile error: LongitudeNumer != LatitudeNumber
    

    How it works

    What this does is simply fool Typescript into believing a primitive number is really some extension of the Number interface (what I call a number type), while actually the primitive number is never converted to an actual object that implements the number type. Conversion is not necessary since the number type behaves like a primitive number type; a number type simply is a number primitive.

    The trick is simply casting to any, so that typescript stops type checking. So the above code can be rewritten to:

    function doArithmeticAndLog(lat : LatitudeNumber) {
        console.log( lat * 2);
    }
    
    doArithmeticAndLog(100);
    

    As you can see the function calls are not even really necessary, because a number and its number type can be used interchangeably. This means absolutely zero performance or memory loss needs to be incurred at run-time. I'd still strongly advise to use the function calls, since a function call costs close to nothing and by casting to any yourself you loose type safety (e.g doArithmeticAndLog('bla') will compile, but will result in a NaN logged to the console at run-time)... But if you want full performance you may use this trick.

    It can also work for other primitive like string and boolean.

    Happy typing!

提交回复
热议问题