Discriminate between empty object type and other concrete types

后端 未结 2 1601
轮回少年
轮回少年 2021-01-21 00:15

All of this compiles without error:

interface Empty { }
interface MaybeEmpty { a?: number; }

var one: Object = 20;
var two: Empty = 21;
var three: {} = 22;
var          


        
相关标签:
2条回答
  • 2021-01-21 00:56

    Update for TS2.4+, there is now weak type detection which will prevent assigning to all-optional-property types like MaybeEmpty if there are no overlapping properties, so var four: MaybeEmpty = 23 is now not permitted.


    Update for TS2.2+, there is now an object type which specifically excludes primitives. Object-like types using curly braces like {} or { a?: number } are still compatible with primitives, as mentioned below.


    Original answer:

    No.

    The root problem is that JavaScript often treats primitive values as if they were their Object-typed equivalents (String/Number/etc) when used in a context where an object is required (e.g. property access).

    TypeScript models this using the "apparent type" whereby objects of a primitive type appear to have the members of their corresponding Object types. This is because, intuitively, code like this should compile without error:

    function fn(x: { toString(): string }) { /* ... */ }
    fn(32); // Should be OK because (32).toString() is OK
    fn('foo'); // Should be OK because ('foo').toString() is OK
    

    It's a small step to this code:

    function fn(x: { toString(): string; other?: whatever; }) { /* ... */ }
    fn(32); // Should be OK
    fn('foo'); // Should be OK
    

    And another small step to this code:

    function fn(x: { other?: whatever; }) { /* ... */ }
    fn(32); // OK, I guess?
    fn('foo'); // OK, I guess?
    

    There's an issue on GitHub tracking the general problem that an interface with all optional properties is effectively never the source of an error when assigning to it. It's a very hard thing to spec.

    0 讨论(0)
  • 2021-01-21 01:17

    Necromancing.
    Had this problem myselfs.
    This is actually possible in TypeScript 2.2+.

    You need to add & object to your type-definition

    e.g.

    var varname: YourInterface & object = something;
    

    in your case:

    interface Empty { }
    interface MaybeEmpty { a?: number; }
    interface MaybeEmptyAndMore { a?: number; [x:string]: any; }
    
    
    var one: Object = 20;
    var two: Empty = 21;
    var three: {} = 22;
    var four: MaybeEmpty & object = 23;
    var foura: MaybeEmpty & object = {};
    var fourb: MaybeEmpty & object = { a: "23"};
    var fourc: MaybeEmpty & object = { abc: "23"};
    var fourd: MaybeEmptyAndMore & object = { abc: "123", def: "456" };
    

    By the way, if it could be a string or an interface (object), then:

    obj: string | YourInterface & object
    

    e.g.

    class Cookie
    {
        constructor(nameOrSettings: string | ICookieSettings  & object )
        {
            if(typeof(nameOrSettings)  === 'string')
            { do_something();}
            else if(typeof(nameOrSettings) === 'object')
            {
                 var obj:ICookieSettings = <ICookieSettings><any>nameOrSettings;
            }
            else throw TypeError("foo");
        }
    }
    
    0 讨论(0)
提交回复
热议问题