19

When using generics in TypeScript, you sometimes see a type Parameter such as:

T extends string

Isn’t this the same as using string directly? Can you subclass string? What would this be good for?

Golo Roden
  • 140,679
  • 96
  • 298
  • 425
  • 3
    I've not seen `T extends string` and honestly it doesn't make sense - `string` is a primitive, you can't even subclass it. You can subclass `String` - the object. – VLAZ Aug 03 '19 at 21:00
  • 4
    You can't subclass it in JavaScript directly, but out of the entire universe of `string`s there are sub-sets of that universe that are contained within it (for example, only the strings `'upper' | 'lower' | 'proper'`) – Sean Vieira Aug 03 '19 at 21:23
  • 5
    It's a gotcha of the language that `T extends string` actually means `T` narrows the type `string`. – GOTO 0 Aug 04 '19 at 01:19
  • 4
    Values of type `'yes' | 'no'` are compatible with **all** string operations but in addition they have the property that if the value is not `'yes'` it has to be `'no'`. The type `'yes' | 'no'` thus _extends_ type `string` by giving stronger guarantees than the type it is based on. – cyberixae Sep 15 '19 at 18:10

2 Answers2

17
type narrowedString =  "foo" | "bar"

// type ExtendsString = true
type ExtendsString = "foo" extends string ? true : false 

"foo" and "bar" extend both the string type. For example that is useful, when you want to define enum like data structures (without the built in TypeScript enum type) or constants.

When a function offers a generic type parameter which extends string like T extends string, you can use that to enforce strong typing for your enums/constants.

function doSomething<T extends string>(t: T): {a: T} {
...
}

// invoke it
doSomething("foo" as const) // return {a: "foo"}

Don't make the mistake to lump extends in TypeScript's typesystem together with extends from ES classes - they are two complete distinct operators.

class extends corresponds to instanceof and exists in the runtime as a construct, whereas in the type system extends e.g. occurs with Conditional types,can be translated with "is assignable to" and is only for compile time.

Update:

You can find out more about String Literal Types in the TypeScript docs.

bela53
  • 3,040
  • 12
  • 27
  • 2
    <<"foo" and "bar" extend both the string type>> I think it would be more correct to say that narrowedString extends the string type (specifically by forbidding values other than "foo" or "bar) – hugo Aug 03 '19 at 21:20
  • I think its correct to say that. You can literally code `"foo" extends string ? true : false ` in TypeScript, see the example above. Also narrowedString is called a type *alias*. – bela53 Aug 03 '19 at 21:25
  • Alright, so you would say "foo" is a type in itself? Or does 'extends' mean 'instanceof' in that case? – hugo Aug 03 '19 at 21:28
  • Yes I would say that. In above case narrowedString would be rather the union type of the two narrowed string subtypes "foo" and "bar". And extends in the typesystem and extends for ES classes are two complete distinct operators. – bela53 Aug 03 '19 at 23:27
7

Here is a simple example of how you might extend a string and how hard coding the type would break it.

type Foo = string & Partial<{ meta: string }>

const foo: Foo = 'foo';
foo.meta = 'this string is important';

function hardCoded(x: string): string {
  return x;
}

hardCoded(foo).meta;  // this is an error

function generic<X extends string>(x: X): X {
  return x;
}
generic(foo).meta;  // but this works
cyberixae
  • 843
  • 5
  • 15
  • 1
    Actually, it seems at least some JavaScript implementations refuse to store properties on a string. So, while this works on the type level it probably won't work at runtime. – cyberixae Dec 13 '21 at 12:05
  • Correct, basically iiuc what happens when you say `const s = ""; s.field = true;` is that a temporary `new String(s)` is created and then `.field` is assigned on *it* but then that temporary *object* [created to box/wrap the original primitive] is immediately discarded and thus the field doesn't stick around. But inspired by what you had here I kept looking and via https://stackoverflow.com/a/66085193/179583 I found this blog post https://codemix.com/opaque-types-in-javascript/ that uses a `type Opaque = T & { __TYPE__: K }` helper to basically create a **separate** (strict) string type. – natevw Jun 24 '23 at 22:38