Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding a stab at some notes for extra topics #309

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions sessions/TypeScript-100/wip/custom_types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
Type Aliases

You can create your own custom types and then combine these to allow reuse.

(this example uses literal types to limit the values to those defined)
*/
type Flavoured = { flavour: ("Strawberry" | "Chocolate" | "Banana") };
type Sized = { size: ("S" | "M" | "L") };
type Scooped = { scoops: (1 | 2 | 3) };

/*
The & syntax creates a type which is the intersection of two existing ones.
*/
type Milkshake = Flavoured & Sized;
let milkshake: Milkshake = {
flavour: "Strawberry",
size: "L"
}

let brokenMilshake: Milkshake = { // error: missing required property from Sized
flavour: "Chocolate"
}

/*
The | syntax creates a type which is the union of two existing ones.
*/
type IceCream = Flavoured & ( Sized | Scooped );
let dessert: IceCream = {
flavour: "Chocolate",
scoops: 3
};

let strangeDessert: IceCream = {
flavour: "Banana",
size: "S"
// metaphor breaks down here since you can say:
, scoops: 3
// which would make little sense.
}

/*
All of these are Types and so they don't exist at runtime - `typeof` and
`instanceof` checks won't work with them.
*/
console.log(typeof dessert); // object
console.log(dessert instanceof IceCream); // Compile error: IceCream is a type not a value.

/*
If a type needs to have some optional properties, this is supported with
the `name?: type` syntax.

You can also add readonly fields with the `readonly` keyword.
*/
type Customer = {
name: string,
email: string,
mobile: string,
landline?: string // because it's the 21st century
readonly dateOfBirth: Date
};

let c: Customer = {
name: "Frank",
email: "[email protected]",
mobile: "3482398472",
dateOfBirth: new Date("1970-01-01T00:00:00.000Z")
}

c.dateOfBirth = new Date(); // can't edit a readonly field

/*
As well as declaring variables of your types, you can create classes
which extend them.
*/
class CustomerObj implements Customer {
name: string;
email: string;
mobile: string;
landline?: string;
dateOfBirth: Date;

constructor (name: string, email: string, mobile: string, dob: Date) {
this.name = name;
this.email = email;
this.mobile = mobile;
this.dateOfBirth = dob;
}
}

let cObj: Customer = new CustomerObj("name", "email", "mobile", new Date());
27 changes: 27 additions & 0 deletions sessions/TypeScript-100/wip/function_types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
Function Types

Since functions are first class in JS you can create function variables,
and TypeScript supports this by letting you define function types:

(param: type, param: type) => type
*/

let add: (a: number, b: number) => number;
add = (a, b) => a + b;

function combineNumbers(
x: number,
y: number,
combiner: (a: number, b: number) => number
): number {
return combiner(x, y);
}

console.log( combineNumbers(10, 20, add) );

console.log(
combineNumbers(10, 20,
(a, b) => a * b // types inferred as numbers
)
);
102 changes: 102 additions & 0 deletions sessions/TypeScript-100/wip/generics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
Generics

Generics give us a way to write a function which can operate on any
type but still gives us type safety.

For example the following function returns a random element from an
array of numbers:
*/
function getRandomNumberElement(items: number[]): number {
let randomIndex = Math.floor(Math.random() * items.length);
return items[randomIndex];
}
let numbers = [1, 5, 7, 4, 2, 9];
let randomNumber: number = getRandomNumberElement(numbers);

// But how can we change this to support strings as well?
let strings = ["a", "b", "c"];
let randomString: string;
// randomString = getRandomNumberElement(strings); // won't compile

/*
We could write the same function again with the types as string[] and
string, but this adds duplicated code.

We could change the existing function to use the any type:
*/
function getRandomAnyElement(items: any[]): any {
let randomIndex = Math.floor(Math.random() * items.length);
return items[randomIndex];
}
let x: any = getRandomAnyElement(strings); // typeof x is any

/*
But now we've lost our type information and we'd need to force the any
back into a type we know to use it safely.

This would be simple enough here, but could get very fiddly with more
complicated functions and would add a lot of verbose casts.

The solution is to make the function Generic.
This preserves the type safety of our function and allows easy reuse.
*/
function getRandomElement<T>(items: T[]): T {
let randomIndex = Math.floor(Math.random() * items.length);
return items[randomIndex];
}
let y: number = getRandomAnyElement(numbers);
let z: string = getRandomElement(strings);

/*
Generics can allow you to limit the arguments to a function based on
inheritance.
*/
function displayPerson<T extends Person>(per: T): void {
console.log(`Person: ${per.name}, ${per.age}`);
}

function displayName<T extends Named>(named: T): void {
console.log(`Named: ${named.name}`);
}

interface Named {
name: string
}
class Person implements Named {
name: string;
age: number;

constructor (name: string, age: number) {
this.name = name;
this.age = age;
}
}

let dee = new Person("Dee", 42);
displayPerson(dee); // Type safe operation a Person
displayName(dee); // Type safe operation a Named object

/*
Generics can also be used to create general purpose classes.

You can then reuse the class with a variety of types as needed, but without
resorting to passing around `any` variables and sacrificing type safety.
*/
class Pair<K, V> {
key: K;
value: V;

constructor (key: K, value: V) {
this.key = key;
this.value = value;
}
}

let pair = new Pair(1, "things");
let k1: number = pair.key;
let v1: string = pair.value;

let pair2 = new Pair(2, new Pair("nested", "pair"));
let k2: number = pair2.key;
let v2: Pair<string, string> = pair2.value;
82 changes: 82 additions & 0 deletions sessions/TypeScript-100/wip/index_signatures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
Index Signatures

Index signatures in TypeScript give you a way of dealing with types where
you know the shape of the type, but you're not sure what all of the
properties will be.
This feature lets you keep your type safety when accessing the properties
of the object by index - without this, you'd get any back.
*/
function averageGrades(
studentMarks: {
[index: string]: number
}
): number {
let total = 0,
count = 0;
for (const subject in studentMarks) {
let subjectMarks: number = studentMarks[subject];
total += subjectMarks;
count++;
}
return (count > 0) ? total / count : 0;
}

let studentMarks = {
science: 85,
maths: 35,
english: 45
};
let studentAverageMark: number = averageGrades(studentMarks);

let studentMarks2 = {
name: "Charlie",
science: 25,
art: 95,
music: 105,
};
let studentAverageMark2: number = averageGrades(studentMarks2);

/*
You can achieve a similar thing using the Record<Keys, Type> utility type.
*/
function averageGrades2(
studentMarks: Record<string, number>
): number {
let total = 0,
count = 0;
for (const subject in studentMarks) {
let subjectMarks: number = studentMarks[subject];
total += subjectMarks;
count++;
}
return (count > 0) ? total / count : 0;
}

averageGrades2(studentMarks);
averageGrades2(studentMarks2);

/*
You can use this as well as named properties.
*/
interface Indexed {
id: number;
name: string;
[index: number]: string;
};

let x: Indexed = {
id: 1,
name: "thing",
42: "test"
}
let y: string = x[42]; // type information provided by index signature.

/*
This won't work if you declare that you're using string keys.
Then you're effectively giving conflicting type information.
*/
interface BrokenIndexed {
id: number;
[index: string]: string;
}
Loading