eolas/neuron/350d8e85-5088-4534-b982-f889275f1449/Further_examples_of_TS_generics.md

148 lines
4.2 KiB
Markdown
Raw Normal View History

2024-10-19 11:00:03 +01:00
---
tags:
- typescript
---
# Further examples of generics in TypeScript
## Basic function
In the code below we have a simple JavaScript function that receives a value and
an an array as parameters. It returns a new array comprising the original array
plus the additional value:
```js
function generateArray(existingArray, newValue) {
return [...existingArray, newValue];
}
```
Imagine that we want to ensure that each of the parameters share the same data
type. In other words: if the function is passed a string array, the second
parameter must also be a string. For example, it should not be the case that you
can append a string to an array of numbers.
Now imagine that we don't know in advance what type the value or array will be,
we just know that the data types of the parameters must match.
In converting the function to TypeScript, one way of overcoming our lack of
foreknowledge would be to deploy `any`. This way it doesn't matter which types
are passed to the function:
```ts
function generateArray(existingArray: any[], newValue: any): any[] {
return [...existingArray, newValue];
}
```
But this is no solution at all. The problem — as always with `any` — is that it
strips our function of any type checks whatsoever and would therefore invite
calls of form: `generateArray([1,2,3], 'lorem')`.
Enter generics:
```ts
function generateArray<T>(existingArr: T[], newValue: T): T[] {
return [...existingArr, newValue];
}
```
Now, whilst we haven't asserted ahead of time which types will be used,
whichever types we do pass in, must match. The function header is saying:
- both arguments must be of the same type (represented by `T`)
- the function will return an array of this same `T` type.
If I then tried to run the function with unmatched types (for example
`generateArray([1,2,3,4], true)` ) TypeScript would raise the following error:
```
Argument of type 'boolean' is not assignable to parameter of type 'number'
```
Note that even though the function in question does not express any preference
for number types, given that our first parameter is a number, TypeScript knows
that the second parameter must also be a number.
> In the generic function we have used `T` as our placeholder for a generic type
> as this is the convention. However there is no compunction to do so. We could
> have used any letter or string, providing that the string is not a reserved
> term.
### More advanced function
This example demonstrates how we can use generics to reduce repetition when
writing functions and is also a more realistic use case.
Let's say we have two types or interfaces:
```tsx
type VideoFormatUrls = {
format720p: URL;
format1080p: URL;
};
```
```tsx
type SubtitleFormatUrls = {
english: URL;
german: URL;
};
```
An example of an object matching these type definitions:
```tsx
const videoFormats: VideoFormatUrls = {
format720p: https://www.format720p.co.uk,
format1080p: https://www.format1080p.co.uk
}
```
Imagine we wanted to be able to check whether a given film is available in a
certain video format. We could write a function like this:
```tsx
function isFormatAvailable(
obj: VideoFormatUrls,
format: string
): format is keyof VideoFormatUrls {
return format in obj;
}
```
Now imagine that we need to do the same thing with subtitles, but given that
`isFormatAvailable()` is typed to the `VideoFormatUrls` type we would get an
error if we used this function for subtitles. But we also don't want to write a
near identical function typed to `SubtitleFormatUrls` to subtitles just to
ensure adequate type safety.
Alternatively we could use a union type, for example:
```tsx
function isFormatAvailable(
obj: VideoFormatUrls | SubtitleFormatUrls,
format: string
): format is keyof VideoFormatUrls {
return format in obj;
}
```
But this quickly becomes unwieldy if we, for the sake of argument have a great
many URL types that we want the function to utilise.
This is where generics become super helpful. Here is how we would rewrite the
function as a generic:
```tsx
function isAvailable<Formats>(obj: Formats, key: string): key is keyof Formats {
return key in obj;
}
```
We could then explicitly type our calls of this function, viz:
```tsx
isFormatAvailable<SubtitleFormatUrls>(subtitles, "english");
```