2

I am guessing it means empty object, but not sure...

I can’t find that type on https://www.typescriptlang.org/docs/handbook/basic-types.html.

interface Component<P = {}, S = {}, SS = any> extends ComponentLifecycle<P, S, SS> { }
sunknudsen
  • 6,356
  • 3
  • 39
  • 76
  • The first two instances mean empty object. –  Feb 24 '20 at 18:09
  • @Amy Thanks! Do you know where I can find a reference of that in the docs? – sunknudsen Feb 24 '20 at 18:10
  • Does this answer your question? [Difference between 'object' ,{} and Object in TypeScript](https://stackoverflow.com/questions/49464634/difference-between-object-and-object-in-typescript) – Guerric P Nov 21 '21 at 16:43

2 Answers2

7

The type {} does not exactly mean an "empty object", because other types of object - which may not be empty - are assignable to the type {}. For example:

function acceptsEmpty(obj: {}): void {
    console.log(obj);
}

let notEmpty: {a: string} = {a: 'test'};

// no error; {a: string} is assignable to {}
acceptsEmpty(notEmpty);

So essentially, the type {} means "not required to have any properties, but may have some", and likewise the type {a: string} means "must have a property named a whose value is a string, but may have other properties too".

So {} imposes almost no constraints on its values; the only rule is that it can't be null or undefined. It is similar to the type object in this regard, except that object also forbids primitive values, while {} allows them:

// no error
let anything: {} = 1;

// error: Type '1' is not assignable to type 'object'.
let noPrimitivesAllowed: object = 1;

Playground Link

kaya3
  • 47,440
  • 4
  • 68
  • 97
  • Thanks for the answer. Do you know whey I am getting the following error then? `Type 'SFC' is not assignable to type 'string | SFC<{}> | ComponentClass<{}, any>'`. – sunknudsen Feb 24 '20 at 18:40
  • 1
    Because even though `AnchorProps` is a subtype of `{}`, the generic type `SFC` may not be defined in such a way that `SFC` is a subtype of `SFC<{}>`. Compare e.g. the way that `List` is not a subtype of `List` in Java, because a list of objects allows adding strings but a list of integers doesn't. You would have to ask another question, and show the definitions of `SFC` and `AnchorProps`. – kaya3 Feb 24 '20 at 18:43
  • Thanks for your suggestion. Done. https://stackoverflow.com/questions/60382399/why-is-type-sfcanchorprops-not-assignable-to-type-sfc – sunknudsen Feb 24 '20 at 19:04
  • Your answer is not totally correct. The `{}` type **is** empty object. The fact that many things are assignable to it is not because `{}` may have properties, but because everything that **extends** this type is assignable to it. – Guerric P Nov 18 '21 at 10:18
  • @GuerricP I don't know what distinction you are trying to draw between an object being assignable to a type `A`, and an object having a type `B` which extends `A`. Those are equivalent conditions. Either way, things of type `{}` are not necessarily empty objects. The set of objects assignable to `{}` is not the set of empty objects. – kaya3 Nov 18 '21 at 10:34
  • It's just how you formulate your answer: *The type {} does not exactly mean an "empty object"* yes it does. You just don't explain how the assignability is related to the extendability – Guerric P Nov 18 '21 at 13:51
  • The type `{}` does *not* exactly mean "empty object", because not every member of that type is an empty object. I honestly don't understand your objection. Compare with the type `[]` which *does* exactly mean "empty array", because all members of that type are empty arrays. – kaya3 Nov 18 '21 at 14:53
  • The type `[]` means "empty array" and everything that extends it is assignable to it, exactly like `{}`: https://www.typescriptlang.org/play?#code/MYGwhgzhAECiAeAXApgOwCbPQQQE67AE9pkk10Y8DCAeVZAN2VwD5oBvAWACho-oQaAOaIAFgC5oABmgBeaQG4e-aAHsxzAAq5VABznQA5BFUBbZCiSGl3AL48ewVagiJoYfEUkBtALoH6AHc4MgwsKiIFIA – Guerric P Nov 18 '21 at 20:13
  • @GuerricP I don't know what you think your example shows, but your `ExtendedArray` is an empty array so *of course* it is a member of the type of empty arrays. On the other hand, `{a: 'test'}` is not an empty object so it is not a member of the type of empty objects, but it *is* a member of the type defined by `{}`; so therefore the type of empty objects and the type `{}` are not the same type, because they do not have the same members. This is really straightforward. – kaya3 Nov 19 '21 at 05:58
  • I think the only issue here is that you have your own, quite bizarre definition of what a type is, where a member of a subtype is not necessarily a member of the supertype. According to your definition, I can equally say that the type `string` means, *exactly*, empty strings; and the fact that `'foo'` is assignable to `string` does not contradict this because `'foo'` is assignable to the literal type `'foo'`, which extends `string`. This is obviously silly, but it's entirely the same argument as you are making. – kaya3 Nov 19 '21 at 06:03
  • *`{a: 'test'}` is not an empty object so it is not a member of the type of empty objects* this is where I don't agree: `{a: 'test'}` is an empty object with an additional `a` property. It **extends** the empty object type so it **is** a member of the empty object type. Anyway I won't spend more time discussing this here, I've upvoted you wrongly. – Guerric P Nov 19 '21 at 11:08
  • *"an empty object with an additional `a` property"* - I am flabbergasted. If an object has any properties then obviously it is not empty. If that is genuinely your understanding of the word "empty" then all I can say is, no. – kaya3 Nov 19 '21 at 17:24
  • This answer is wrong but it’s not your fault, it’s bad language design from TS authors. For whatever reason, {} accepts any non-nullish value, and they chose to call it {} instead of `nonnullish` or whatever – Andy May 03 '23 at 04:00
  • @Andy The answer already says exactly that: *"So `{}` imposes almost no constraints on its values; the only rule is that it can't be `null` or `undefined`."* The reason that `{}` accepts primitive values like `string` is because to do otherwise would be inconsistent; we would certainly expect `{length: number, toUpperCase(): string}` to be compatible with primitive strings, and `{}` is a supertype of that. – kaya3 May 03 '23 at 14:23
  • Sorry I skimmed, and when I saw "So essentially, the type {} means "not required to have any properties, but may have some" it sounded like it was implying it still has to be an object. I don't think any comparison to other object shape types like `{a: string}` is helpful because TS doesn't flag excess property errors on `{}`. It really has nothing to do with object types. – Andy May 03 '23 at 18:10
  • @Andy Interesting, I didn't know that excess property checks are disabled for `{}`. Still, excess property checks are a separate thing, not a type error ─ in general an object with excess properties *is* assignable to the type, it's just that the compiler will warn you about those excess properties when there is no type-safe way that those properties could be accessed. – kaya3 May 03 '23 at 22:20
  • @kaya3 This is why `{}` is a really bad name for the "anything but null or undefined" type. So many people misunderstand what `{}` actually means because it looks like an object. Also, TS generates errors for excess properties, not warnings, even with strict type-checking disabled. But excess property errors don't happen in all cases of assignments, in general TS soundness guarantees are fairly weak and there's been a longstanding discussion about supporting exact object types. – Andy May 04 '23 at 03:53
  • @kaya3 I didn't realize though that TS allows assigning primitives to objects, allowing `const baz: {length: number} = 'hello'`, which is...interesting... – Andy May 04 '23 at 04:02
  • @kaya3 I had always assumed TS object types meant the value was truly an object, not a primitive. Now I see that they just mean a value that has the given properties, and the `{}` type makes more sense. But it's weird because `string`, `number`, and `boolean` are more magic than just a set of properties. – Andy May 04 '23 at 04:14
  • @Andy `{}` may be an unfortunate "name" for this type, but it necessarily follows from the fact that primitives can be assignable to other "object" types like `{length: number}` that they must also be assignable to `{}`. For what it's worth, primitives being assignable to "object" types is useful for "branded primitives" like `number & {__unit?: 'meters'}`, where the intersection would be `never` if `number` and `{...}` had no overlap. – kaya3 May 04 '23 at 14:55
  • Hmmm I guess so… it’s surprising for anyone used to type systems that treat primitives and objects as totally distinct, especially since primitives in JS are still somewhat different from first class objects – Andy May 05 '23 at 18:20
2

Component<P = {}, S = {}, SS = any> means that the default types for P and S are empty objects.

So you can use the interface with 0..3 generic params like so:

const a: Component  // = Component<{},{},any>
const b: Component<SomeType>  // = Component<Some,{},any>
const c: Component<SomeType, SomeOtherType>  // = Component<SomeType, SomeOtherType, any>
const d: Component<SomeType, SomeOtherType, []>  // = Component<SomeType, SomeOtherType, []>
Andreas Dolk
  • 113,398
  • 19
  • 180
  • 268
  • Thanks for the answer. Does this mean `P` defaults to an empty object and is of type `object` vs `SS` which defaults to type `any` and is of type `any`? Having a hard time wrapping my mind around this one. – sunknudsen Feb 24 '20 at 18:26
  • 1
    @sunknudsen No it does not default to anything. It is not a definition, it is a type. – Michelangelo Feb 24 '20 at 18:30