It's not guaranteed, but an interesting idea to entertain
function name (obj) {
return obj?.name;
}
function init (opts) {
const isEnabled = opts.enabled ?? true;
}
Use advanced JavaScript features like optional-chaining and null-coalescing operators without worrying about browser support.
Nominal Typing: Is X an instance of type Y?
Structural Typing: Checks against the structure, only cares about the shape of an object.
TypeScript has structural typing system
Examples: All strongly-typed languages
Examples: Haskell, OCaml
TypeScript has structural typing system
class Dog {
breed: string;
constructor(breed: string) {
this.breed = breed;
}
}
function printDog(dog: Dog) {
console.log("Dog: " + dog.breed);
}
class Cat {
breed: string;
constructor(breed: string) {
this.breed = breed;
}
}
const kitten = new Cat("Maine Coon");
printDog(kitten);
function print ({ name }: { name: string; }) {
console.log(name);
}
function print (dog: Dog) {
console.log(dog.name);
}
function print (name: string) {
console.log(name);
}
any
any[]
string[]
[string, string, string]
["abc", string, string]
....
never
const greet1 = "Hello";
let greet2 = "Hello";
function print (value: typeof greet1) {
// ...
}
test(greet1); // OK
test(greet2); // ERROR
interface User {
name: string;
age: number;
}
type LiterallyHello = 'Hello';
const user = User; // ERROR
const hello = LiterallyHello; // ERROR
let value = '1234';
let another: value; // ERROR
class User {
// ...
}
const user = User;
const another: User = new User();
3 Steps
😱
function wrap (value) {
return {
value
};
};
const num = wrap(10);
const str = wrap("hi");
num.value; // ???
str.value; // ???
How can we type the `wrap` function?
function wrap<T>(value: T) {
return {
value
};
};
const num = wrap(10);
const str = wrap("hi");
num.value; // number
str.value; // string
function filter<T>(arr: T[], fn: (item: T) => boolean) {
const acc: T[] = [];
for (let i = 0; i < arr.length; i++) {
if (fn(arr[i])) {
acc.push(arr[i]);
}
}
return acc;
}
const test = [1, 2, 3, 4, 5];
// X will be treated as a number
filter(test, x => x % 2 === 0);
function map<T, U>(arr: T[], fn: (item: T) => U) {
const acc: U[] = [];
for (let i = 0; i < arr.length; i++) {
acc.push(fn(arr[i]));
}
return acc;
}
const test = [1, 2, 3, 4, 5];
// X will be treated as a number
const out = map(test, x => x % 2 === 0); // array of booleans
function useMutation<TData = any, TVars = QueryVariables>(mutation) {
// ...
}
Exhaustive Conditionals and Type Guards
any: Literally anything
Types that can hold any type of values
unknown: Literally 🤷♂️
const anything: any = 1231231;
const nani: unknown = 'Hello there!';
console.log(anything.asdhhasd.asdasd);
console.log(nani.split('')); // ERROR
const nani: unknown = 'Hello there!';
if (typeof nani === 'string') {
console.log(nani.split('')); // OK
}
(nani as string).split(''); // OK
never: ERROR IN THE SIMULATION
Types that cannot hold any type of values
let x: string | number = '123';
if (typeof x === 'string') {
// In this block, x is a string
x.split('');
} else if (typeof x === 'number') {
// In this block, x is a number
x.toFixed(2);
} else {
// In this block, x is a `never`
// because there is no way it happens
x; // never
throw new Error('Someone messed up!');
}
export type KnownKeys<T> = {
[K in keyof T]: string extends K ? never : number extends K ? never : K;
} extends { [_ in keyof T]: infer U }
? U
: never;
interface User {
name: string;
age: number;
[k: string]: string;
}
type UserKnownKeys = KnownKeys<User>;
This is a type that accepts an interface and extracts only the named keys from it