2

In existing code, I've come across type parameter declarations like this: <Param extends {}>.

My question is, what is the meaning of the extends {} part? How does it differ from simply <Param>?


Example:

type Fold<S extends {}, D extends {}> = {
  folder: ...
}
P Varga
  • 19,174
  • 12
  • 70
  • 108
  • Difficult to say without the code, maybe to prevent primitive type such as string, number, undefined... Do you have an example? – HTN Jun 24 '20 at 12:10
  • 1
    @HTN: That does not exclude primitives, `object` would do that. – H.B. Jun 24 '20 at 12:19
  • 1
    @HTN [it doesn't prevent primitives](https://www.typescriptlang.org/play/#code/GYVwdgxgLglg9mABDAJgHgCqIKYA8rZgoDOiA3gL4B8AFLgFyIYCUjWZAsAFACQATtigg+SXAG5uFbtK4AbQYjAgAtoyXKARtj6IAvMhQ0ArMwlyFxKH0aW+MMAHM9BmgCIAFtlmy4r093koRDgNACtGENDsaGdUGjJEYDg4RlcNAEM+V0QKUyA). I don't actually know what it does, though. It's a lower bound for absolutely anything. Might prevent `never`, not sure. – VLAZ Jun 24 '20 at 12:19
  • 1
    [doesn't filter out `never`, either](https://www.typescriptlang.org/play/#code/GYVwdgxgLglg9mABDAJgHgCqIKYA8rZgoDOiA3gL4B8AFLgFyIYCUjWZAsAFACQATtigg+SXAG5uFbtK4AbQYjAgAtoyXKARtj6IAvMhQ0ArMwlyFxKH0aW+MMAHM9BmgCIAFtlmy4r093koRDgNACtGENDsaGdUGjJEYDg4RlcNAEM+V0QKfy5uFGjZTOxEQMVsADc1Ku0zcrA4AAdS-TiwKuYgA). I'm curious for this usage, as well. Might just be an artefact of *something* being there before but later removed. Or a pattern so you can quickly fill in a property in the future. – VLAZ Jun 24 '20 at 12:22
  • Can you give an example? – ford04 Jun 24 '20 at 15:23
  • 1
    @ford04 Added an example, is that sufficient? – P Varga Jun 25 '20 at 21:32

1 Answers1

4

{} vs unknown constraint

If you write <S>, S is unconstrained - it behaves like <S extends unknown> (TS 3.5+).

{} type can take every value (incl. primitives), except null or undefined with strictNullChecks.

unknown is more or less {} | null | undefined, making {} a more narrow subtype:

type FoldImplicit<S> = S
type FoldObject<S extends {}> = S

type T1 = FoldImplicit<"foo"> // ✅ 
type T2 = FoldImplicit<undefined> // ✅
type T3 = FoldObject<"foo"> // ✅
type T4 = FoldObject<undefined> // ❌ - `undefined` not in `{}` (strictNullChecks)
// compiles with `strictNullChecks` disabled 
// - null and undefined now belong to every type.

Where do I see these constructs?

The most often encountered case is probably with React generic components:

// help compiler to distinguish JSX tag from generic component declaration
const Comp = <T extends {}>(props: { foo: T }) => <div>...</div>

To match implicit constraints more closely, you can write <T extends unknown> or just <T,> (note the comma). Before TS 3.5, the implicit constraint has been {}.

With extends {}, you can also take away undefined or null as possible inputs. But in my opinion a) it makes code unnecessarily verbose b) you better find a more narrow constraint for stronger types.

Playground sample

ford04
  • 66,267
  • 20
  • 199
  • 171