You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -146,6 +146,30 @@ Here, TypeScript is telling us that `42` is not a `string`. This is because we'v
146
146
147
147
Passing type arguments is an instruction to TypeScript override inference. If you pass in a type argument, TypeScript will use it as the source of truth. If you don't, TypeScript will use the type of the runtime argument as the source of truth.
148
148
149
+
### There Is No Such Thing As 'A Generic'
150
+
151
+
A quick note on terminology here. TypeScript 'generics' has a reputation for being difficult to understand. I think a large part of that is based on how people use the word 'generic'.
152
+
153
+
A lot of people think of a 'generic' as a part of TypeScript. They think of it like a noun. If you ask someone "where's the 'generic' in this piece of code?":
154
+
155
+
```typescript
156
+
const identity = <T>(arg:T) =>arg;
157
+
```
158
+
159
+
They will probably point to the `<T>`. Others might describe the code below as "passing a 'generic' to `Set`":
160
+
161
+
```typescript
162
+
const set =newSet<number>([1, 2, 3]);
163
+
```
164
+
165
+
This terminology gets very confusing. Instead, I prefer to split them into different terms:
166
+
167
+
- Type Parameter: The `<T>` in `identity<T>`.
168
+
- Type Argument: The `number` passed to `Set<number>`.
169
+
- Generic Class/Function/Type: A class, function or type that declares a type parameter.
170
+
171
+
When you break generics down into these terms, it becomes much easier to understand.
172
+
149
173
### The Problem Generic Functions Solve
150
174
151
175
Let's put what we've learned into practice.
@@ -268,7 +292,7 @@ Our `TObj` type parameter, when used without a constraint, is treated as `unknow
268
292
To fix this, we can add a constraint to `TObj` that ensures it has an `id` property:
@@ -280,7 +304,7 @@ Now, when we use `removeId`, TypeScript will error if we don't pass in an object
280
304
const result =removeId({ name: "Alice" }); // red squiggly line under name
281
305
282
306
// hovering over name shows:
283
-
// Object literal may only specify known properties, and 'name' does not exist in type '{ id: any; }'
307
+
// Object literal may only specify known properties, and 'name' does not exist in type '{ id: unknown; }'
284
308
```
285
309
286
310
But if we pass in an object with an `id` property, TypeScript will know that `id` has been removed:
@@ -526,11 +550,9 @@ This error shows us that we're trying to call `searchMusic` with two arguments,
526
550
527
551
### Function Overloads vs Unions
528
552
529
-
<!-- CONTINUE -->
530
-
531
-
Function overloads can be useful when you want to express multiple ways to call a function that spread out over different parameters. In the example above, we can either call the function with separate arguments or with a single object.
553
+
Function overloads can be useful when you have multiple call signatures spread over different sets of arguments. In the example above, we can either call the function with one argument, or three.
532
554
533
-
When you have the same number of arguments but different types, you can use a union type instead of function overloads. For example, if you want to allow the user to search by either artist name or criteria object, you could use a union type:
555
+
When you have the same number of arguments but different types, you should use a union type instead of function overloads. For example, if you want to allow the user to search by either artist name or criteria object, you could use a union type:
534
556
535
557
```typescript
536
558
function searchMusic(
@@ -540,12 +562,14 @@ function searchMusic(
540
562
// Search by artist
541
563
searchByArtist(query);
542
564
} else {
543
-
// Search by genre
544
-
searchByGenre(query.genre);
565
+
// Search by all
566
+
search(query.artist, query.genre, query.year);
545
567
}
546
568
}
547
569
```
548
570
571
+
This uses far fewer lines of code than defining two overloads and an implementation.
As it currently is, the keys and values for the Map can be of any type.
562
-
563
-
However, the goal is to make this function generic so that we can pass in a type argument to define the type of the values in the `Map`.
585
+
As it currently stands, we get back a `Map<any, any>`. However, the goal is to make this function generic so that we can pass in a type argument to define the type of the values in the `Map`.
564
586
565
587
For example, if we pass in `number` as the type argument, the function should return a `Map` with values of type `number`:
typetest=Expect<Equal<typeofunknownMap, Map<string, unknown>>>; // red squiggly line under Equal<>
598
620
```
599
621
600
-
Your task is to transform `createStringMap` into a generic function capable of accepting a type argument for the values of Map. Make sure it functions as expected for the provided test cases.
622
+
Your task is to transform `createStringMap` into a generic function capable of accepting a type argument to describe the values of Map. Make sure it functions as expected for the provided test cases.
The function accepts an array as an argument, then converts it to a `Set`, then returns it as a new array. This is a common pattern for when you want to have unique values inside your array.
626
648
627
-
While this function operates effectively at runtime, it lacks type safety. It currently allows an array of `any` type, and as seen in the tests, the return type is also typed as `any`:
649
+
While this function operates effectively at runtime, it lacks type safety. It transforms any array passed in into `any[]`.
628
650
629
651
```typescript
630
652
it("returns an array of unique values", () => {
@@ -644,9 +666,9 @@ it("should work on strings", () => {
644
666
});
645
667
```
646
668
647
-
Your task is to boost the type safety of the `uniqueArray` function. To accomplish this, you'll need to incorporate a type parameter into the function.
669
+
Your task is to boost the type safety of the `uniqueArray` function by making it generic.
648
670
649
-
Note that in the tests, we do not explicitly provide type arguments when invoking the function. TypeScript should be able to deduce the type from the argument.
671
+
Note that in the tests, we do not explicitly provide type arguments when invoking the function. TypeScript should be able to infer the type from the argument.
650
672
651
673
Adjust the function and insert the necessary type annotations to ensure that the `result` type in both tests is inferred as `number[]` and `string[]`, respectively.
//Property 'code' does not exist on type 'TError'.
669
691
```
670
692
671
693
If the incoming error doesn't include a `code`, the function assigns a default `UNKNOWN_CODE`. Currently there is an error under the `code` property.
@@ -733,9 +755,7 @@ In short, the thing that we get back from `safeFunction` should either be the th
733
755
734
756
However, there are some issues with the current type definitions.
735
757
736
-
The `PromiseFunc` type is currently set to always return `Promise<any>`, which doesn't provide much information.
737
-
738
-
Also, the function returned by `safeFunction` is supposed to return either the result of `func` or an `Error`, but at the moment, it's just returning `Promise<any>`.
758
+
The `PromiseFunc` type is currently set to always return `Promise<any>`. This means that the function returned by `safeFunction` is supposed to return either the result of `func` or an `Error`, but at the moment, it's just returning `Promise<any>`.
739
759
740
760
There are several tests that are failing due to these issues:
741
761
@@ -791,9 +811,9 @@ async (...args: any[]) => {
791
811
};
792
812
```
793
813
794
-
Since the thing being passed into `safeFunction` can receive arguments, the function we get back should also contain those arguments and require you to pass them in.
814
+
Now that the function being passed into `safeFunction` can receive arguments, the function we get back should _also_ contain those arguments and require you to pass them in.
795
815
796
-
However, as seen in the tests, the type is currently a bit too wide:
816
+
However, as seen in the tests, this isn't working:
797
817
798
818
```typescript
799
819
it("should return the result if the function succeeds", async () => {
@@ -852,41 +872,6 @@ it("should return the result if the function succeeds", async () => {
852
872
853
873
Update the types of the function and the generic type, and make these tests pass successfully.
854
874
855
-
### Exercise 7: Type Predicates
856
-
857
-
Here we have an `isString` function that accepts an input of `unknown` type, and returns a boolean based on whether the `input` is of type string or not:
858
-
859
-
```typescript
860
-
const isString = (input:unknown) => {
861
-
returntypeofinput==="string";
862
-
};
863
-
```
864
-
865
-
In this case, the `unknown` type is appropriate as we don't possess any prior knowledge about the type of `input`.
866
-
867
-
The function is then applied in the context of a `filter` function:
868
-
869
-
```typescript
870
-
const mixedArray = [1, "hello", [], {}];
871
-
872
-
const stringsOnly =mixedArray.filter(isString);
873
-
```
874
-
875
-
We would anticipate that the `filter` function would return an array of strings since it only retains the elements from `mixedArray` that pass the `isString` test.
876
-
877
-
However, this doesn't work as expected on the type level.
878
-
879
-
We end up with an array of empty objects instead, because an empty object is being passed to the `isString` function, and all the other types are assignable to an empty object.
880
-
881
-
```typescript
882
-
// hovering over stringsOnly shows:
883
-
const stringsOnly: {}[];
884
-
```
885
-
886
-
In order to make `isString` function as we expect, we need to use a type guard function and add a type predicate to it.
887
-
888
-
Your task is to adjust the `isString` function to incorporate a type guard and type predicate that will ensure the `filter` function correctly identifies strings as well as assigning the accurate type to the output array.
889
-
890
875
### Exercise 8: Assertion Functions
891
876
892
877
This exercise starts with an interface `User`, which has properties `id` and `name`. Then we have an interface `AdminUser`, which extends `User`, inheriting all its properties and adding a `roles` string array property:
Through these steps, we've successfully transformed `createStringMap` from a regular function into a generic function capable of handling type arguments.
969
+
Through these steps, we've successfully transformed `createStringMap` from a regular function into a generic function capable of receiving type arguments.
985
970
986
971
### Solution 2: Default Type Arguments
987
972
@@ -1001,7 +986,7 @@ Now when we call `createStringMap()` without a type argument, we end up with a `
1001
986
const stringMap =createStringMap();
1002
987
1003
988
// hovering over stringMap shows:
1004
-
const stringMap:<string>() =>Map<string, string>;
989
+
const stringMap:Map<string, string>;
1005
990
```
1006
991
1007
992
If we attempt to assign a number as a value, TypeScript gives us an error because it expects a string:
The function's return type is an array of `T`, where `T` represents the type of elements supplied to the function.
1079
+
The function's return type is an array of `T`, where `T` represents the type of elements in the array supplied to the function.
1095
1080
1096
-
Thus, TypeScript can infer the return type as `number[]` for an input array of numbers, or `string[]` for an input array of strings, even without explicit return type annotation. As we can see, the tests pass successfully:
1081
+
Thus, TypeScript can infer the return type as `number[]` for an input array of numbers, or `string[]` for an input array of strings, even without explicit return type annotations. As we can see, the tests pass successfully:
This change ensures that `addCodeToError` must be called with an object that includes a `message` string property. TypeScript also knows that `code` could either be a number or `undefined`. If `code` is absent, it will default to `UNKNOWN_CODE`.
1152
1117
1153
-
These constraints have our tests passing, including the case where we pass in an extra `filepath` property. This is because using `extends` in generics does not restrict you to only passing in the properties defined in the constraint.
1118
+
These constraints make our tests pass, including the case where we pass in an extra `filepath` property. This is because using `extends` in generics does not restrict you to only passing in the properties defined in the constraint.
1154
1119
1155
1120
### Solution 5: Combining Generic Types and Functions
Whatever we pass into `safeFunction` will be inferred as the type argument for `PromiseFunc`. This is because the type argument is being inferred as the identity of the generic function.
1173
+
Whatever we pass into `safeFunction` will be inferred as the type argument for `PromiseFunc`. This is because the type argument is being inferred _inside_ the generic function.
1174
+
1175
+
This combination of generic functions and generic types can make your generic functions a lot easier to read.
1209
1176
1210
1177
### Solution 6: Multiple Type Arguments in a Generic Function
You might have tried this with `unknown[]` - but `any[]` is the only thing that works in this scenario.
1198
+
1230
1199
Now we need to update the `safeFunction` so that it has the same arguments as `PromiseFunc`. To do this, we'll add `TArgs` to its type parameters.
1231
1200
1232
1201
Note that we also need to update the args for the `async` function to be of type `TArgs`:
@@ -1246,32 +1215,6 @@ This change is necessary in order to make sure the function returned by `safeFun
1246
1215
1247
1216
With these changes, all of our tests pass as expected.
1248
1217
1249
-
The big takeaway is that when you're doing inference with function parameters, you want to make sure that you're capturing the entire arguments as a tuple with `TArgsextendsany[]` instead of using just an array `TArgs[]`. Otherwise, you won't get the correct type inference.
1250
-
1251
-
### Solution 7: Type Predicates
1252
-
1253
-
For the `isString` function, we know that the `input` will be a string, but TypeScript can't infer that logic on its own. To help, we can add a type predicate that says `inputisstring`:
0 commit comments