Skip to content

Commit df9d7da

Browse files
committed
Initial, WIP attempt
0 parents  commit df9d7da

39 files changed

+2189
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
.vscode

README.md

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<a href="https://totaltypescript.com"><img src="./og-image.png" /></a>
2+
3+
## Work in progress!
4+
5+
This repository is a work-in-progress. Exercises may appear out of order, and there will likely be a lot of content inside the [`FUTURE`](./notes/FUTURE.md) notes.
6+
7+
You'll still be able to run through the exercises, but expect some bumps along the way!
8+
9+
## Quickstart
10+
11+
Clone this repo or [open in Gitpod](https://gitpod.io/#https://github.com/total-typescript/typescript-generics-tutorial).
12+
13+
```sh
14+
# Installs all dependencies
15+
yarn install
16+
17+
# Starts the first exercise
18+
yarn exercise 01
19+
20+
# Runs linting and tests on the solution
21+
yarn solution 01
22+
```
23+
24+
## How to take the course
25+
26+
You'll notice that the course is split into exercises. Each exercise is split into a `*.problem.ts` and a `*.solution.ts`.
27+
28+
To take an exercise:
29+
30+
1. Go into `*.problem.ts`
31+
2. Run `yarn exercise 01`, where `01` is the number of the exercise you're on.
32+
33+
The `exercise` script will run TypeScript typechecks and a test suite on the exercise.
34+
35+
This course encourages **active, exploratory learning**. In the video, I'll explain a problem, and **you'll be asked to try to find a solution**. To attempt a solution, you'll need to:
36+
37+
1. Check out [TypeScript's docs](https://www.typescriptlang.org/docs/handbook/intro.html)
38+
2. Try to find something that looks relevant.
39+
3. Give it a go to see if it solves the problem.
40+
41+
You'll know if you've succeeded because the tests will pass.
42+
43+
**If you succeed**, or **if you get stuck**, unpause the video and check out the `*.solution.ts`. You can see if your solution is better or worse than mine!
44+
45+
You can run `yarn solution 01` to run the tests and typechecking on the solution.
46+
47+
## Acknowledgements
48+
49+
Say thanks to Matt on [Twitter](https://twitter.com/mattpocockuk) or by joining his [Discord](https://discord.gg/8S5ujhfTB3). Consider signing up to his [Total TypeScript course](https://totaltypescript.com).
50+
51+
## Reference
52+
53+
### `yarn exercise 01`
54+
55+
Alias: `yarn e 01`
56+
57+
Run the corresponding `*.problem.ts` file.
58+
59+
### `yarn solution 01`
60+
61+
Alias: `yarn s 01`
62+
63+
Run the corresponding `*.solution.ts` file. If there are multiple, it runs only the first one.

notes/FUTURE.md

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# TypeScript Generics Tutorial
2+
3+
## Things to add
4+
5+
1. Default generics
6+
1. Generic constraints
7+
1. Using default generics to compute variables
8+
1. F.Narrow
9+
1. Multiple generics
10+
1. When to use any in highly dynamic generic signatures
11+
1. Spotting useless generics
12+
1. Generics in template literal types
13+
1. Inferring number/boolean from template literal types
14+
1. Infer
15+
1. Generics in type predicates and assertion functions
16+
1. Using function overloads to specify different generic signatures
17+
1. Generics in object primitive types
18+
1. Generics in classes
19+
1. this is this & {}
20+
1. Multiple generics per object
21+
22+
## Things NOT to add
23+
24+
These things are out-of-scope, or will be included in other modules
25+
26+
- What are function overloads?
27+
- Converting union types to object types to make them faster (via key mapping and Extract)
28+
29+
<!-- - limits of typeof narrowing (unknown and object)
30+
- Type predicates
31+
- Using external libraries and @types
32+
- discriminated unions
33+
- narrowing on discriminated unions
34+
- Template literals
35+
- Branded types
36+
- Readonly properties on objects
37+
- Index signatures
38+
- `keyof`
39+
- Readonly, Exclude, Extract (union-based or readonly-based utility types)
40+
- let vs const vs as const
41+
- Function overloads
42+
- typeof
43+
- Generics in functions -->

og-image.png

163 KB
Loading

package.json

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "beginners-typescript",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"author": "Matt Pocock <[email protected]>",
6+
"license": "GPL",
7+
"devDependencies": {
8+
"@types/node": "^18.6.5",
9+
"chokidar": "^3.5.3",
10+
"cross-fetch": "^3.1.5",
11+
"typescript": "^4.8.2",
12+
"vitest": "^0.21.1"
13+
},
14+
"scripts": {
15+
"exercise": "node scripts/exercise.js",
16+
"e": "yarn exercise",
17+
"solution": "SOLUTION=true node scripts/exercise.js",
18+
"s": "yarn solution"
19+
},
20+
"dependencies": {
21+
"@types/express": "^4.17.13",
22+
"express": "^4.18.1",
23+
"ts-toolbelt": "^9.6.0",
24+
"zod": "^3.17.10"
25+
}
26+
}

scripts/exercise.js

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
const { execSync } = require("child_process");
2+
const fs = require("fs");
3+
const path = require("path");
4+
const chokidar = require("chokidar");
5+
6+
const srcPath = path.resolve(__dirname, "../src");
7+
const tsconfigPath = path.resolve(__dirname, "../tsconfig.json");
8+
9+
const [, , exercise] = process.argv;
10+
11+
if (!exercise) {
12+
console.log("Please specify an exercise");
13+
process.exit(1);
14+
}
15+
16+
const allExercises = fs.readdirSync(srcPath);
17+
18+
let pathIndicator = ".problem.";
19+
20+
if (process.env.SOLUTION) {
21+
pathIndicator = ".solution.";
22+
}
23+
24+
const exercisePath = allExercises.find(
25+
(exercisePath) =>
26+
exercisePath.startsWith(exercise) && exercisePath.includes(pathIndicator),
27+
);
28+
29+
if (!exercisePath) {
30+
console.log(`Exercise ${exercise} not found`);
31+
process.exit(1);
32+
}
33+
34+
const exerciseFile = path.resolve(srcPath, exercisePath);
35+
36+
// One-liner for current directory
37+
chokidar.watch(exerciseFile).on("all", (event, path) => {
38+
const fileContents = fs.readFileSync(exerciseFile, "utf8");
39+
40+
const containsVitest = fileContents.includes("vitest");
41+
try {
42+
console.clear();
43+
if (containsVitest) {
44+
console.log("Running tests...");
45+
execSync(`vitest run "${exerciseFile}" --passWithNoTests`, {
46+
stdio: "inherit",
47+
});
48+
}
49+
console.log("Checking types...");
50+
execSync(`tsc "${exerciseFile}" --noEmit --strict`, {
51+
stdio: "inherit",
52+
});
53+
console.log("Typecheck complete. You finished the exercise!");
54+
} catch (e) {
55+
console.log("Failed. Try again!");
56+
}
57+
});

scripts/setup.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { fetch } from "cross-fetch";
2+
3+
global.fetch = fetch;

src/10-dry-interfaces.problem.ts

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* How can we refactor these to reduce the duplication
3+
* in the 'data' declaration?
4+
*/
5+
6+
import { Equal, Expect } from "./helpers/type-utils";
7+
8+
export interface UserData {
9+
data: {
10+
id: string;
11+
firstName: string;
12+
lastName: string;
13+
};
14+
}
15+
16+
export interface PostData {
17+
data: {
18+
title: string;
19+
};
20+
}
21+
22+
export interface CommentData {
23+
data: {
24+
comment: string;
25+
};
26+
}
27+
28+
type tests = [
29+
Expect<
30+
Equal<
31+
UserData,
32+
{
33+
data: {
34+
id: string;
35+
firstName: string;
36+
lastName: string;
37+
};
38+
}
39+
>
40+
>,
41+
Expect<
42+
Equal<
43+
PostData,
44+
{
45+
data: {
46+
title: string;
47+
};
48+
}
49+
>
50+
>,
51+
Expect<
52+
Equal<
53+
CommentData,
54+
{
55+
data: {
56+
comment: string;
57+
};
58+
}
59+
>
60+
>,
61+
];

src/10-dry-interfaces.solution.1.ts

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* How can we refactor these to reduce the duplication
3+
* in the 'data' declaration?
4+
*/
5+
6+
import { Equal, Expect } from "./helpers/type-utils";
7+
8+
export interface DataContainer<TData> {
9+
data: TData;
10+
}
11+
12+
export type UserData = DataContainer<{
13+
id: string;
14+
firstName: string;
15+
lastName: string;
16+
}>;
17+
18+
export type PostData = DataContainer<{
19+
title: string;
20+
}>;
21+
22+
export type CommentData = DataContainer<{
23+
comment: string;
24+
}>;
25+
26+
type tests = [
27+
Expect<
28+
Equal<
29+
UserData,
30+
{
31+
data: {
32+
id: string;
33+
firstName: string;
34+
lastName: string;
35+
};
36+
}
37+
>
38+
>,
39+
Expect<
40+
Equal<
41+
PostData,
42+
{
43+
data: {
44+
title: string;
45+
};
46+
}
47+
>
48+
>,
49+
Expect<
50+
Equal<
51+
CommentData,
52+
{
53+
data: {
54+
comment: string;
55+
};
56+
}
57+
>
58+
>,
59+
];

src/10-dry-interfaces.solution.2.ts

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* How can we refactor these to reduce the duplication
3+
* in the 'data' declaration?
4+
*/
5+
6+
import { Equal, Expect } from "./helpers/type-utils";
7+
8+
export type DataContainer<TData> = {
9+
data: TData;
10+
};
11+
12+
export type UserData = DataContainer<{
13+
id: string;
14+
firstName: string;
15+
lastName: string;
16+
}>;
17+
18+
export type PostData = DataContainer<{
19+
title: string;
20+
}>;
21+
22+
export type CommentData = DataContainer<{
23+
comment: string;
24+
}>;
25+
26+
type tests = [
27+
Expect<
28+
Equal<
29+
UserData,
30+
{
31+
data: {
32+
id: string;
33+
firstName: string;
34+
lastName: string;
35+
};
36+
}
37+
>
38+
>,
39+
Expect<
40+
Equal<
41+
PostData,
42+
{
43+
data: {
44+
title: string;
45+
};
46+
}
47+
>
48+
>,
49+
Expect<
50+
Equal<
51+
CommentData,
52+
{
53+
data: {
54+
comment: string;
55+
};
56+
}
57+
>
58+
>,
59+
];
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Equal, Expect } from "./helpers/type-utils";
2+
3+
const returnWhatIPassIn = (t: unknown) => {
4+
return t;
5+
};
6+
7+
const one = returnWhatIPassIn(1);
8+
const matt = returnWhatIPassIn("matt");
9+
10+
type tests = [Expect<Equal<typeof one, 1>>, Expect<Equal<typeof matt, "matt">>];

0 commit comments

Comments
 (0)