├── LICENSE
└── README.md
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Levent Arman Özak
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TypeScript Questions
2 |
3 | Back in April and May 2023, I asked daily TypeScript questions on [my Twitter account](https://twitter.com/armanozak) for 30 working days. This repository is originally a compilation of these questions, but if you want to contribute, please feel free to create a PR.
4 |
5 | ## Q1
6 |
7 | ```typescript
8 | const foo = "1234";
9 | ```
10 |
11 | What is the type of `foo`?
12 |
13 |
14 | Answer
15 |
16 | `"1234"`
17 |
18 | TypeScript infers this type as a string literal and not as string because it is a constant. If we had used let, then the inferred type would have been string.
19 |
20 | Can we force TypeScript to use `string` here? Yes.
21 |
22 | ```typescript
23 | const foo: string = "1234"
24 | ```
25 |
26 | Info: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types
27 |
28 |
29 |
30 | ## Q2
31 |
32 | ```typescript
33 | enum Flag {
34 | Foo,
35 | Bar = -2,
36 | Baz
37 | }
38 | ```
39 |
40 | What are the values of `Foo` and `Baz` flags?
41 |
42 | > Note: This is just an example to test your knowledge. I recommend assigning all enum values explicitly, even when they are ordered.
43 |
44 |
45 | Answer
46 |
47 | **Foo = 0, Baz = -1**
48 |
49 | When an enum member doesn't have an initializer, it is assigned as previous member value +1 or 0 if it is the first member.
50 |
51 | Info: https://www.typescriptlang.org/docs/handbook/enums.html#computed-and-constant-members
52 |
53 |
54 |
55 | ## Q3
56 |
57 | ```typescript
58 | const myObject = {
59 | id: crypto.randomUUID(),
60 | };
61 |
62 | type Id = ;
63 | // ^
64 | // What should I write here to create a type alias
65 | // for the id property of myObject?
66 | ```
67 |
68 | I want to create a type alias for `myObject`'s `id` property. How should I fill in the blank to do this?
69 |
70 |
71 | Answer
72 |
73 | `typeof myObject.id`
74 |
75 | We want to keep the `Id` type alias compatible with the actual type of that property, so we can't;
76 |
77 | - use hard-coded types.
78 | - leverage any technique that gets the return type of randomUUID (because it can change too).
79 |
80 | Info: https://www.typescriptlang.org/docs/handbook/2/typeof-types.html
81 |
82 |
83 |
84 | ## Q4
85 |
86 | ```typescript
87 | function add(x: number, y: number) {
88 | return x + y;
89 | }
90 |
91 | const params = [1, 2];
92 |
93 | add(...params);
94 | // ^
95 | // I'm getting an error here. How can I make it go away?
96 | ```
97 |
98 | I want to spread those parameters, but I'm getting an error. What should I do?
99 |
100 |
101 | Answer
102 |
103 | ```typescript
104 | const params: Parameters = [1, 2];
105 | ```
106 |
107 | Can't we use a tuple instead? Yes, assigning `[number, number]` to or using `as const` on params will work too. However, deriving the type from the parameters of the `add` function will always keep them in sync.
108 |
109 | Info: https://www.typescriptlang.org/docs/handbook/utility-types.html#parameterstype
110 |
111 |
112 |
113 | ## Q5
114 |
115 | ```typescript
116 | interface Foo {
117 | x: number;
118 | y: number;
119 | }
120 |
121 | interface Bar {
122 | q: string;
123 | }
124 |
125 | let fooOrBar: Foo | Bar;
126 |
127 | fooOrBar = { q: "" };
128 | type T1 = typeof fooOrBar;
129 |
130 | fooOrBar = { x: 0, y: 1 };
131 | type T2 = typeof fooOrBar;
132 | ```
133 |
134 | What are `T1` and `T2`?
135 |
136 |
137 | Answer
138 |
139 | **T1 = Bar, T2 = Foo**
140 |
141 | The type of `fooOrBar` is a union type (`Foo | Bar`). However, TypeScript is able to narrow types based on assignments. So, `T1` becomes Bar. Then we assign another value and TypeScript narrows the type again, this time as `Foo`.
142 |
143 | Info: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#assignments
144 |
145 |
146 |
147 | ## Q6
148 |
149 | ```typescript
150 | class SplitAfterEach {
151 | constructor(public readonly search: string) {}
152 |
153 | [Symbol.split](str: string) {
154 | const result: string[] = [];
155 |
156 | do {
157 | let index = str.indexOf(this.search);
158 | if (index < 0) {
159 | result.push(str);
160 | break;
161 | }
162 |
163 | index += this.search.length;
164 | result.push(str.substring(0, index));
165 | str = str.substring(index);
166 | } while (str.length);
167 |
168 | return result;
169 | }
170 | }
171 |
172 | console.log("foobarfoobar".split(new SplitAfterEach("foo")));
173 |
174 | type StringKeysOfSplitAfterEach = ;
175 | // ^
176 | // How can I get only the string keys of SplitAfterEach?
177 | ```
178 |
179 | How should I fill in the blank to get only the string keys of `SplitAfterEach`?
180 |
181 |
182 | Answer
183 |
184 | **Option 1:** `keyof SplitAfterEach & string`
185 |
186 | We can get keys with `keyof`, but keys can be of `number` or `symbol` type too. An intersection type narrows it for us.
187 |
188 | Info: https://www.typescriptlang.org/docs/handbook/2/objects.html#intersection-types
189 |
190 | **Option 2:** Use a utility type
191 |
192 | `Extract`
193 |
194 | Info: https://www.typescriptlang.org/docs/handbook/utility-types.html#extracttype-union
195 |
196 |
197 |
198 | ## Q7
199 |
200 | ```typescript
201 | type X = Awaited>>;
202 | ```
203 |
204 | What is `X`?
205 |
206 |
207 | Answer
208 |
209 | `string`
210 |
211 | `Awaited` is a utility type that unwraps promises recursively.
212 |
213 | Info: https://www.typescriptlang.org/docs/handbook/utility-types.html#awaitedtype
214 |
215 |
216 |
217 | ## Q8
218 |
219 | ```typescript
220 | type BuildEvent = "BuildError" | "BuildSuccess";
221 | type HttpEvent = "HttpError" | "HttpSuccess";
222 | type InitEvent = "InitError" | "InitSuccess";
223 | type NavEvent = "NavError" | "NavSuccess";
224 |
225 | type AppEvent = BuildEvent | HttpEvent | InitEvent | NavEvent;
226 |
227 | type AppError = Extract;
228 | // ^
229 | // What should I write here to get all error types?
230 | // i.e. "BuildError" | "HttpError" | "InitError" | "NavError"
231 | ```
232 |
233 | How should I fill in the blank to get all error types and none of the success types?
234 |
235 |
236 | Answer
237 |
238 | `` `${string}Error` ``
239 |
240 | Template literals allow us to compose string literal types, and string acts like a wildcard. So, when `Extract` asserts the assignability of the `AppEvent` union against `${string}Error`, all members ending with "Error" pass.
241 |
242 | Info: https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html
243 |
244 |
245 |
246 | ## Q9
247 |
248 | ```typescript
249 | type BuildEvent = "BuildError" | "BuildSuccess";
250 | type HttpEvent = "HttpError" | "HttpSuccess";
251 | type InitEvent = "InitError" | "InitSuccess";
252 | type NavEvent = "NavError" | "NavSuccess";
253 |
254 | type AppEvent = BuildEvent | HttpEvent | InitEvent | NavEvent;
255 |
256 | // This works. The type is a union of all errors.
257 | type AppError = Extract;
258 |
259 | // This doesn't! The type is never.
260 | type AppNever = AppEvent extends `${string}Error` ? AppEvent : never;
261 |
262 | // Why?
263 | ```
264 |
265 | In the last question, we used `Extract` to get all error types. [`Extract` is a very simple generic type](https://github.com/microsoft/TypeScript/blob/main/src/lib/es5.d.ts):
266 |
267 | `type Extract = T extends U ? T : never;`
268 |
269 | However, when I implement the same conditional type directly, it doesn't work. Why?
270 |
271 |
272 | Answer
273 |
274 | Generic types distribute unions.
275 |
276 | Every member of a union is evaluated separately when passed to any generic type. So this works too:
277 |
278 | ```typescript
279 | type ExtractError = T extends `${string}Error` ? T : never;
280 | type AppError = ExtractError;
281 | ```
282 |
283 | Info: https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types
284 |
285 |
286 |
287 | ## Q10
288 |
289 | ```typescript
290 | // human.ts
291 | export class Human {
292 | constructor(public readonly name: string) {}
293 |
294 | breath() {
295 | console.log(`${this.name} breaths`);
296 | }
297 |
298 | eat(food: string) {
299 | console.log(`${this.name} eats ${food}`);
300 | }
301 |
302 | sleep() {
303 | console.log(`${this.name} sleeps`);
304 | }
305 |
306 | walk() {
307 | console.log(`${this.name} walks`);
308 | }
309 | }
310 | ```
311 |
312 | ```typescript
313 | // speech.ts
314 | import { Human } from "./human";
315 |
316 | Human.prototype.speak = function (this: Human, sth: string) {
317 | // ^
318 | // I'm getting an error: Property 'speak' does not exist on type 'Human'.
319 |
320 | console.log(`${this.name} says "${sth}"`);
321 | };
322 |
323 |
324 | // How can I add the "speak" method the "Human" interface?
325 | ```
326 |
327 | ```typescript
328 | // index.ts
329 | import { Human } from "./human";
330 | import "./speech";
331 |
332 | const human = new Human("Guru");
333 |
334 | human.speak("Hello world!");
335 | ```
336 |
337 | How can I add a `speak` method to the `Human` interface using the _speech_ module?
338 |
339 |
340 | Answer
341 |
342 | With module augmentation.
343 |
344 | Placing the code below in _speech.ts_ will add the speak method to the `Human` interface:
345 |
346 | ```typescript
347 | declare module './human' {
348 | interface Human {
349 | speak(str: string): void;
350 | }
351 | }
352 | ```
353 |
354 | Info: https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
355 |
356 |
357 |
358 | ## Q11
359 |
360 | ```typescript
361 | /* Note: This file is imported by the entry file. */
362 |
363 | import * as MSW from "msw";
364 |
365 | (global as any).msw = MSW;
366 |
367 | // How can I add "msw" to the global scope in TypeScript?
368 | ```
369 |
370 | I want to use the MSW library in my test files without importing it in each file, so I mutated the global object. How can I add `msw` to the global scope in TypeScript too?
371 |
372 |
373 | Answer
374 |
375 | With global augmentation.
376 |
377 | Adding this to the _test-globals.ts_ file will declare `msw` as a global variable:
378 |
379 | ```typescript
380 | declare global {
381 | const msw: typeof MSW;
382 | }
383 | ```
384 |
385 | Info: https://www.typescriptlang.org/docs/handbook/declaration-merging.html#global-augmentation
386 |
387 |
388 |
389 | ## Q12
390 |
391 | ```typescript
392 | type MapLink = string;
393 |
394 | interface Address {
395 | street: string;
396 | no: string;
397 | postalCode: string;
398 | city: string;
399 | country: string;
400 | }
401 |
402 | interface Coordinates {
403 | x: number;
404 | y: number;
405 | }
406 |
407 | declare function getLinkForAddress(address: Address): MapLink;
408 | declare function getLinkForCoordinates(coordinates: Coordinates): MapLink;
409 |
410 | function getLink(addressOrCoordinates: Address | Coordinates) {
411 | if (isCoordinates(addressOrCoordinates)) {
412 | // ^
413 | // This doesn't narrow the type to Coordinates and I get errors.
414 | return getLinkForCoordinates(addressOrCoordinates);
415 | }
416 |
417 | return getLinkForAddress(addressOrCoordinates);
418 | }
419 |
420 | // What should I add to isCoordinates function to narrow the type?
421 | function isCoordinates(input: object) {
422 | return "x" in input && "y" in input;
423 | }
424 | ```
425 |
426 | I am getting errors when I pass `addressOrCoordinates` as an argument to `getLinkForCoordinates` and `getLinkForAddress` functions. What should I add to `isCoordinates` function to narrow the type?
427 |
428 |
429 | Answer
430 |
431 | `input is Coordinates` as return type
432 |
433 | ```typescript
434 | function isCoordinates(
435 | input: object
436 | ): input is Coordinates {
437 | return "x" in input && "y" in input;
438 | }
439 | ```
440 |
441 | This kind of return type is called a "type predicate".
442 |
443 | Info: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates
444 |
445 |
446 |
447 | ## Q13
448 |
449 | ```typescript
450 | interface Petrol {
451 | engineType: "combustion";
452 | fillTank(): void;
453 | }
454 |
455 | interface Diesel {
456 | engineType: "combustion";
457 | fillTank(): void;
458 | }
459 |
460 | interface Electric {
461 | engineType: "electricity";
462 | charge(): void;
463 | }
464 |
465 | interface Hybrid {
466 | engineType: "combustion & electricity";
467 | fillTank(): void;
468 | charge(): void;
469 | }
470 |
471 | type CarType = Petrol | Diesel | Electric | Hybrid;
472 |
473 | function getArea(car: CarType) {
474 | switch (car.engineType) {
475 | case "combustion":
476 | return car.fillTank();
477 | case "electricity":
478 | return car.charge();
479 | default:
480 | return exhaustCases(car);
481 | // ^
482 | // How can I declare this function so that I get a type error here?
483 | }
484 | }
485 | ```
486 |
487 | I want to get a type error when not all cases are exhausted. How should I declare the `exhaustCases` function for that? Please use the `declare function ...` syntax.
488 |
489 |
490 | Answer
491 |
492 | ```typescript
493 | declare function exhaustCases(
494 | input: never
495 | ): never;
496 | ```
497 |
498 | `never` is assignable to all types, but no other type is assignable to `never`. If the case clauses in your switch statement exhaust all members of the union type, TypeScript will narrow the type in the default clause down to `never`, so there will be no errors. In this example, the `Hybrid` interface is not exhausted, so we would get an error if we try to assign it to a parameter with `never` type.
499 |
500 | Info: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#exhaustiveness-checking
501 |
502 | > Why is the return type `never` and not `void`? 🤔
503 | >
504 | > Well, in this example, it is possible to return void. However, we would have to update it if we ever update the return types of `fillTank` and `charge` methods, otherwise we would end up with a union type for no reason. Take a look at this playground: https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgAoTFA9gG2QbwChlkIQBzUCAFQE8AHCALmQCIEsBbAIwFcBnMMCwhWAbmLIYwHDmpwQAawAUAShbcsuCAokBfQoVCRYiFABFgEfhDxESZSiBoNmbDjwFCR4ydNnySmoaWjg6IPqGxtDwSMgAomEImMAIBJKOVHSMLKy2EMlQqcBgtL4kCAAWcFDkEMHImtq6hAZG4DFmyAAStNxFACbpDhRZrrkefILCIMgAZKRJKQglZRIk-nIKKuqNoeHryFU1dQ1NYS1tpYzIAMI12SgAvGgY2HgAPsiW1rbIX4kCst-j0+oMJIQYLwQMkZsgoBAYAj+JV4s5arQAMpYXhQJDKBA1Fj3KCPVTIAD0ACo9s1Zl8AG5YYBDKkU4bIfgAdxKVWQBJqADpMs4yRyKnAbO4uFNvKImJISCQEWBcbNCVBBZtAjtDhKpXklkUVqVWAqlUqVWqjkLjrV6qo9cgBoi4LwcGBzRb4RhrRAAB7VLz3Gz8AVQR2SAxtF0IHA1FBQmFy0iBt2CEPWZSgei8T3IZwM6C7JkssRAA
505 | >
506 | > `never` as return type is the best option because it won't change the return type of the function and will always work. So, we can even have a common `exhaustCases` function that works for all switch statements.
507 |
508 |
509 |
510 | ## Q14
511 |
512 | ```typescript
513 | type Operation = "upsert" | "delete";
514 |
515 | interface Options {
516 | skipErrors?: boolean;
517 | }
518 |
519 | // None of these should lead to type errors:
520 | execute("upsert", "foo");
521 | execute("delete", "foo", { skipErrors: false });
522 | execute("upsert", "foo", "bar");
523 | execute("delete", "foo", "bar", { skipErrors: true });
524 | execute("upsert", "foo", "bar", "baz");
525 | execute("delete", "foo", "bar", "baz", {});
526 | execute("upsert", ...(["foo", "bar"] as const));
527 | execute("delete", ...(["foo", "bar"] as const), {});
528 | execute("upsert", "x", ...["foo", "bar", "baz"]);
529 | execute("delete", "x", ...["foo", "bar", "baz"], {});
530 |
531 | // All of these should lead to type errors:
532 | execute("upsert");
533 | execute("delete", {});
534 | execute("upsert", {}, "foo");
535 | execute("delete", 1, "foo");
536 | execute("upsert", "foo", 1, "bar");
537 | execute("delete", "foo", { skipErrors: "true" });
538 | execute("foo", "bar", "baz");
539 |
540 | // How should I declare the execute function?
541 | // Note: Use void as return type.
542 | ```
543 |
544 | How should I declare the `execute` function? You can use `void` as its return type. Please use the `declare function ...` syntax.
545 |
546 |
547 | Answer
548 |
549 | ```typescript
550 | declare function execute(
551 | operation: Operation,
552 | requiredParam: string,
553 | ...optionalParams: string[] | [...string[], Options]
554 | ): void;
555 | ```
556 |
557 | Using an overload here is worse than a union type because of two reasons:
558 |
559 | - Instead of just "skipErrors", TypeScript auto-complete displays all string methods with an "s".
560 | - `execute("upsert", "foo", 1, "bar")` is completely highlighted as an error because it doesn't match any overloads, whereas with union types only the part that doesn't match the signature is highlighted.
561 |
562 | > Please try not to declare a function like that in your work. 😅
563 |
564 |
565 |
566 | ## Q15
567 |
568 | ```typescript
569 | type A = void extends true ? 1 : 0;
570 | type B = true extends void ? 1 : 0;
571 | type X = (() => void) extends (() => true) ? 1 : 0;
572 | type Y = (() => true) extends (() => void) ? 1 : 0;
573 | ```
574 |
575 | What are `A`, `B`, `X`, and `Y`?
576 |
577 |
578 | Answer
579 |
580 | **A = 0, B = 0, X = 0, Y = 1**
581 |
582 | `void` and `true` don't extend each other, but `void` as a return type has a special status to allow this:
583 |
584 | ```typescript
585 | declare const records: string[];
586 | declare function register(rec: string): boolean;
587 | records.forEach(register);
588 | ```
589 |
590 | Info: https://www.typescriptlang.org/docs/handbook/2/functions.html#return-type-void
591 |
592 |
593 |
594 | ## Q16
595 |
596 | ```typescript
597 | class Point {
598 | get #self() {
599 | return this as Mutable;
600 | }
601 |
602 | constructor(
603 | public readonly x: number,
604 | public readonly y: number
605 | ) {}
606 |
607 | move(x: number, y: number) {
608 | this.#self.x += x;
609 | this.#self.y += y;
610 | }
611 |
612 | [Symbol.iterator]() {
613 | return [this.x, this.y][Symbol.iterator]();
614 | }
615 | }
616 |
617 | const point = new Point(0, 1);
618 | console.log(...point); // 0 1
619 |
620 | point.move(2, 3);
621 | console.log(...point); // 2 4
622 | ```
623 |
624 | Please declare the `Mutable` type and make sure it is reusable.
625 |
626 |
627 | Answer
628 |
629 | ```typescript
630 | type Mutable = {
631 | -readonly [K in keyof T]: T[K];
632 | };
633 | ```
634 |
635 | Removing modifiers like `readonly` and optional (`?`) is possible while mapping types.
636 |
637 | Info: https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#mapping-modifiers
638 |
639 |
640 |
641 | ## Q17
642 |
643 | ```typescript
644 | interface Point {
645 | readonly x: number;
646 | readonly y: number;
647 | }
648 |
649 | type Mutable = Reveal<
650 | {
651 | -readonly [K in keyof Pick]: T[K];
652 | } & Omit
653 | >;
654 |
655 | type HorizontallyMovablePoint = Mutable;
656 | // {
657 | // x: number;
658 | // readonly y: number;
659 | // }
660 | ```
661 |
662 | Let's add the option to pick keys to the `Mutable` type in the previous question. What should the `Reveal` type be, if we want to see the whole interface and not some obscure intersection type on hover?
663 |
664 |
665 | Answer
666 |
667 | ```typescript
668 | type Reveal = {
669 | [K in keyof T]: T[K];
670 | } & {};
671 | ```
672 |
673 |
674 |
675 | ## Q18
676 |
677 | ```typescript
678 | interface Point {
679 | [stringIndex: string]: any;
680 | [symbolIndex: symbol]: any;
681 | [templateLiteralIndex: `distanceTo${string}`]: number;
682 | name: string;
683 | readonly x: number;
684 | readonly y: number;
685 | move(x: number, y: number): void;
686 | }
687 |
688 | type BasePoint = OmitIndexSignatures;
689 | // type BasePoint = {
690 | // name: string;
691 | // readonly x: number;
692 | // readonly y: number;
693 | // move: (x: number, y: number) => void;
694 | // }
695 | ```
696 |
697 | How should I declare the `OmitIndexSignatures` type so that `BasePoint` won't have any index signatures?
698 |
699 | Ref: https://www.typescriptlang.org/docs/handbook/2/objects.html#index-signatures
700 |
701 |
702 | Answer
703 |
704 | ```typescript
705 | type OmitIndexSignatures = {
706 | [
707 | K in keyof T
708 | as {} extends Record ? never : K
709 | ]: T[K];
710 | };
711 | ```
712 |
713 | Credit: https://stackoverflow.com/questions/51465182/how-to-remove-index-signature-using-mapped-types/68261113#68261113
714 |
715 |
716 |
717 | ## Q19
718 |
719 | ```typescript
720 | type T0 = Await>; // boolean
721 | type T1 = Await>>; // number
722 | type T2 = Await>>>; // symbol
723 | type T3 = Await, 'then'>>; // string
724 | type T4 = Await<{then(mapFn: (url: URL) => T, flag: boolean): T}>; // URL
725 | type T5 = Await<{then(): Promise}>; // never
726 | type T6 = Await<{then: Event}>; // { then: Event }
727 | type T7 = Await; // Date
728 | type T8 = Await; // null
729 | type T9 = Await; // undefined
730 | ```
731 |
732 | Please declare the `Await` type without using (or peeking at 🙂) the built-in `Awaited` type. You may assume strict mode.
733 |
734 |
735 | Answer
736 |
737 | ```typescript
738 | type Await =
739 | T extends {
740 | then: (fn: infer Fn, ...args: any[]) => any
741 | }
742 | ? Fn extends (value: infer V, ...args: any[]) => any
743 | ? Await
744 | : never
745 | : T;
746 | ```
747 |
748 | Conditional types allow us to infer types with the `infer` keyword.
749 |
750 | Info: https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#inferring-within-conditional-types
751 |
752 |
753 |
754 | ## Q20
755 |
756 | ```typescript
757 | interface Component {
758 | on_mount(): void;
759 | on_update(): void;
760 | on_destroy(): void;
761 | parent_ref?: Component;
762 | }
763 |
764 | type HookName = ParseHookNames;
765 | // ^
766 | // "Mount" | "Update" | "Destroy"
767 | ```
768 |
769 | Please declare the `ParseHookNames` type.
770 |
771 |
772 | Answer
773 |
774 | ```typescript
775 | type ParseHookName =
776 | T extends `on_${infer K}`
777 | ? Capitalize
778 | : never;
779 |
780 | type ParseHookNames = ParseHookName;
781 | ```
782 |
783 | Info: https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html#inference-with-template-literals
784 |
785 |
786 |
787 | ## Q21
788 |
789 | ```typescript
790 | abstract class Child {
791 | play() {};
792 | }
793 |
794 | abstract class Adult {
795 | doWhatYouWant() {};
796 | haveFun() {};
797 | spendTimeWithLovedOnes() {};
798 | rest() {};
799 | }
800 |
801 | class Parent extends Adult {
802 | constructor(public children: [Child, ...Child[]]) {
803 | super();
804 | }
805 | }
806 |
807 | class WorkingAdult extends Working(Adult) {}
808 |
809 | class WorkingParent extends Working(Parent) {
810 | haveSomeTimeOff() {
811 | this.children.forEach(child => child.play());
812 | return super.haveSomeTimeOff();
813 | }
814 | }
815 |
816 | function spendToday(workers: WorkingAdult[]): boolean {
817 | return workers.every(person => person.haveSomeTimeOff());
818 | }
819 | ```
820 |
821 | Please declare the `Working` function.
822 |
823 |
824 | Answer
825 |
826 | ```typescript
827 | type AbstractConstructor = abstract new (...args: any[]) => T;
828 | type AbstractClass = AbstractConstructor & { prototype: T };
829 |
830 | interface WorkingImpl {
831 | work(): void; // just for presentational purposes
832 | haveSomeTimeOff(): boolean;
833 | }
834 |
835 | declare function Working>(
836 | Base: T
837 | ): AbstractClass & T;
838 | ```
839 |
840 | Info: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-2.html#abstract-construct-signatures
841 |
842 |
843 |
844 | ## Q22
845 |
846 | ```typescript
847 | declare const uniqueSymbol: unique symbol;
848 | interface Foo { n: number; }
849 | interface Bar { n: number; }
850 | interface Baz { n: number; a: boolean; }
851 |
852 | type T01 = IsSame; // true
853 | type T02 = IsSame; // true
854 | type T03 = IsSame; // true
855 | type T04 = IsSame; // false
856 | type T05 = IsSame; // false
857 | type T06 = IsSame; // false
858 | type T07 = IsSame; // false
859 | type T08 = IsSame; // false
860 | type T09 = IsSame; // false
861 | type T10 = IsSame; // true
862 | type T11 = IsSame; // false
863 | type T12 = IsSame<1, number>; // false
864 | type T13 = IsSame; // true
865 | type T14 = IsSame; // false
866 | type T15 = IsSame; // true
867 | type T17 = IsSame; // false
868 | type T18 = IsSame<'X', string>; // false
869 | type T19 = IsSame>; // false
870 | type T20 = IsSame, Promise>; // true
871 | type T21 = IsSame; // true
872 | type T22 = IsSame