====== TypeScript Review ======
/*
Inicializar un paquete:
npm init -y
Añadir el compilador typescript
npm add -s typescript
Ejecutar el compilador typescript
npx tsc
Inicializar projecto typescript con codigo en src y salida en lib
npx tsc --init --rootdir src --outdir lib
Ahora la ejecución compila el codigo
npx tsc
Dejarlo compilando
npx tsc --watch
*/
let message: string = "Hello world";
console.log(message);
/*
"use strict";
let message = "Hello world";
console.log(message);
*/
//-- Primitives
let isPresent: boolean = true;
let beast: number = 66.4566;
let anotherNumber: string = beast.toPrecision(1);
let notDefined: undefined = undefined;
let notPresent: null = null;
let penta: symbol = Symbol('star');
let biggy: bigint = 9007199254740991n;
//-- List classes
let array: Array = [1, 2, 3];
let array2: number[] = [1, 2, 3];
let set: Set = new Set([1, 2, 3]);
//-- Generics
class Queue {
private data: Array = [];
push(item: T) { this.data.push(item); }
pop(): T | undefined { return this.data.shift(); }
}
let queue: Queue = new Queue();
queue.push(33);
//-- Tuples
let tuple: [number, number] = [0, 0]; // [0, 0, 0] is an error
//-- Types and type alias
let center: {x: number, y: number} = {
x: 0, y: 0
};
type Point = {x: number, y: number};
let center2: Point = {x: 0, y: 0};
//-- Functions
function add(a: number, b: number): number { return a + b; }
function log(message: string): void { console.log(message); }
function sum(...values: number[]) {}
let addFunction: (a: number, b: number) => number;
addFunction = add;
//-- Structural typing
type User = { id: string };
type Product = { id: string };
let user: User = { id: "1" };
let product: Product = { id: "3" } ;
let user2: User = product; // No error
type Point2D = { x: number, y: number };
type Point3D = { x: number, y: number, z: number};
let p2: Point2D = { x: 0, y: 0 };
let p3: Point3D = { x: 1, y: 1, z: 1 };
p2 = p3; // No error. p3 = p2 <- error
function takePoint2D (point: Point2D) {}
takePoint2D(p3);
//-- Classes
class Animal {
private name: string;
protected position: number;
#last_name: string; // This is also private, but `private` is prefered
constructor(name: string) {
this.name = name;
this.position = 0;
this.#last_name = "Toby";
}
public move(meters: number): void { this.position += meters; }
}
class Bird extends Animal { // Having private position would not work
fly (miles: number): void { this.position += (miles * 0.0006); }
}
let cat = new Animal("cat");
cat.move(10);
//-- Any and Unknown
let exampleAny: any = 333;
let exampleUnknown: unknown = 333; // safer
exampleAny.allow.anything();
let bBoolean: boolean = exampleAny;
// exampleUnknown.does.not.allow();
// exampleUnknown.trim(); // does not allow this either
if (typeof exampleUnknown == 'string') { exampleUnknown.trim() }
//-- Type assertion and casts
/*
test.js contains:
export function load() {
if (3 % 2 == 0) return "caca";
else if (3 % 65 == 0) return AnonymousObject();
return 33;
}
*/
import { load } from "./test.js" // we require to "allowJS" in tsconfig.json
let varLoaded = load();
const trimmed: string = (varLoaded as string); // assertion
let strVariable: string = "1992";
let numVariable: number = +strVariable; // cast
let bVariable: boolean = (numVariable == 1992);
/*
When you want to use an external variable, like process.env from node. We will do a declaration:
declare const process: any;
There is a way to directly define those external variables in a non invasive way:
- You can set them in `env.d.ts` file.
But if you are going to use node typing, the best way is to install the `@types/node` package.
With this, it is not required to import those variables.
For example, with Express.js: `@types/express`
However, for using TypeScript on node we will make use of ts-node package.
JS:
If you create this class:
class Person {
growOld() { this.whatever }
}
And you use a person instance with: const growOld = person.growOld(), `this` is lost.
For maintaining the `this` context growOld must be defined like this:
class Person {
growOld = () => { this.whatever }
}
*/
//-- Readonly
type XValue = {
readonly x: number;
}
let xvalue: XValue = {x: 33}
// xvalue.x = 33; this gives an error
//-- Unions
function format(input: string | string[]) : void {} // Accepts string or string array
//-- Literals
let direction: 'North' | "South" | "East" | "West";
direction = 'North';
// direction = 'north'; error
class Cat { meow() {}}
class Dog { bark() {}}
type Pet = Cat | Dog;
let pet: Pet = new Cat()
if (pet instanceof Cat) pet.meow();
//-- Discrimination
type Circle = {
kind: 'circle', //This can be a boolean. The real example is with isValid: true, and then isValid: False
validatedValue: string,
};
type Square = {
kind: 'rectangle',
errorReason: string,
};
type Figure =
| Circle
| Square;
function logResult(result: Figure) {
if (result.kind == 'circle') {
console.log('Success, validated value:', result.validatedValue);
}
if (result.kind == 'rectangle') {
console.error('Failure, error reason:', result.errorReason);
}
}
//-- parameter properties
class Person {
constructor (public name: string, public age: number) {}
}
const adam = new Person('Adam', 12000);
console.log(adam.name); // Prints Adam
//-- Intersection types
type Another2D = {
x: number,
y: number
}
type Another3D = Another2D & {
z: number
}
let aP3D = {x: 2, y: 3, z: 4}
type AnotherPerson = { name: string }
type Email = { email: string}
function contact(data: AnotherPerson & Email) {}
contact({name: "hola", email: "a@b.com"})
type ContactData = AnotherPerson & Email;
function contact2(data: ContactData) {}
//-- Optionals
type APerson = {name: string, email: string, phone?: string };
const alfred: APerson = {name: "alfred", email: "a@a.com"}
//-- Not null
type Position = {x: number, y?: number | null | undefined}
let p: Position = {x: 33}
console.log(p.y!)
//-- Interfaces
interface Location2D {
x: number,
y: number
}
interface Location2D { // this is merged
xy: string
}
interface Location3D extends Location2D {
z: number
}
let location: Location3D = {x: 0, y: 0, z: 0, xy: "00"}
//-- never
let example: never;
// ----
type Square1 = {
kind: 'square',
size: number,
};
type Rectangle1 = {
kind: 'rectangle',
width: number,
height: number,
};
type Circle1 = {
kind: 'circle',
radius: number,
};
type Shape1 =
| Square1
| Rectangle1
| Circle1;
function area(s: Shape1) {
if (s.kind === 'square') {
return s.size * s.size;
} else if (s.kind === 'rectangle') {
return s.width * s.height;
} else if (s.kind === 'circle') {
return Math.PI * (s.radius ** 2);
}
const _ensureAllCasesAreHandled: never = s; // this is an assert. It will raise a TS error if we do not handle a type
return _ensureAllCasesAreHandled;
}
type Animal = {
name: string,
voice(): string
}
// class Cat implements Animal {} <- it will fail until implement name and voice
//-- Assigment assertion
let dice!: number; // Allows to indicate it could not be a value assigned to it when used
console.log(dice); // It does not raises error
// class Point {x: number} // This gives an error as nothing assigns x. You can define it as x!
//-- Type guards
type Square = {
size: number,
};
type Rectangle = {
width: number,
height: number,
};
type Shape = Square | Rectangle;
// shape is square indicates Typscript that when this is true, shape is Square and will work like that
function isSquare(shape: Shape): shape is Square {
return 'size' in shape;
}
function isRectangle(shape: Shape): shape is Rectangle {
return 'width' in shape;
}
function area(shape: Shape) {
if (isSquare(shape)) {
return shape.size * shape.size;
}
if (isRectangle(shape)) {
return shape.width * shape.height;
}
const _ensure: never = shape;
return _ensure;
}
//-- Function assert
function assert(condition: unknown, message: string): asserts condition {
if (!condition) throw new Error(message);
}
function assertDate(value: unknown): asserts value is Date {
let isDate: boolean = value instanceof Date;
if (!isDate) throw new TypeError("Value is not date")
}
// assert(maybePerson != null, "Could not load a person")
// assertDate(maybePerson.dateOfBirth);
// maybePerson.dateOfBirth.toISOString(); // this would give an error if the previous assert was not done
//-- Function overloading
// We define which value returns a function depending on its input
function makeDate(timestamp: number): Date;
function makeDate(year: number, month: number, day: number): Date;
function makeDate(timestampOrYear: number, month?: number, day?: number): Date {
if (month != null && day != null) {
return new Date(timestampOrYear, month - 1, day);
} else {
return new Date(timestampOrYear);
}
}
const doomsday = makeDate(2000, 1, 1); // 1 Jan 2000
const epoch = makeDate(0); // 1 Jun 1970
// Another way of function overloading
type Add = {
(a: number, b: number): number,
(a: number, b: number, c: number): number,
debugName?: string
}
const add: Add = (a: number, b: number, c?: number) => {
return a + b + (c != null ? c : 0);
}
add.debugName = 'Addition!'
//-- Abstract classes
abstract class Command {
abstract commandLine(): string
}
class GitReset extends Command {
commandLine(): string {
return "reset"
}
}
//-- Typing dictionaries
type Dictionary = {
[key: string]: boolean
}
type CommandDictionary = {
[name: string]: Command
}
const commands: CommandDictionary = {
rset: new GitReset()
}
delete commands["rset"];
commands["reset"] = new GitReset();
commands.resetig = new GitReset();
//-- Inmutable
function reverseSorted(input: readonly number[]): number[] {
// return input.sort(); is error as it mutates the array
return input.slice().sort(); // slice copy the input
}
type Point = readonly [number, number]; // Tuple not mutable
const dave = {
name: "Dave"
} as const;
// dave.name = "Juan" Esto da error por el "as const" que pone fields como readonly
//-- This
// function double () { return this.value * 2; }
// const valid = { value: 10, double, }
// valid.double();
// Estas últimas añaden la double function a valid, pero no se detecta el tipo de valor
// La siguiente es mejor
function double(this: {value: number }) { return this.value * 2; }
//-- Constraints for generics
type NameFields = { firstName: string, lastName: string };
// El tipo T ha de cumplir Namefields y el retorno tendrá fullName
function addFullName(obj: T): T & { fullName: string } {
return {
...obj,
fullName: `${obj.firstName} ${obj.lastName}`,
};
}
const john = addFullName({
email: 'john@example.com',
firstName: 'John',
lastName: 'Doe'
});
console.log(john.email); // john@example.com
console.log(john.fullName); // John Doe
const jane = addFullName({ firstName: 'Jane', lastName: 'Austen' });
//-- Type Generation & Aliases
const center = {x: 0, y: 0, z: 0}
type AnotherPoint = typeof center;
const unit: typeof center = {x: 1, y: 1, z: 1}
// Esto puede ser usado para generar un tipo de lo que devuelve un REST
// Con lo siguiente, indicamos el anidado tipo
type RESTResponse = {
payment: {
creditCardToken: string
}
}
function getPayment(): RESTResponse['payment'] {
return {
creditCardToken: "333333aaa333"
}
}
//-- Get values of keys
type AnimalKeys = keyof Animal;
function logAnimal(obj: any, key: AnimalKeys) {
return obj[key]; // We know key can only be name or voice
}
// With this we limit keys from obj and from values knowing it is good
function setAnimal(obj: Obj, key: Key, value: Obj[Key]) {
obj[key] = value;
}
//-- Conditional type
type IsNumber = T extends number ? 'number' : 'other';
type WithNumber = IsNumber;
type WithOther = IsNumber;
export type TypeName =
T extends string ? 'string' :
T extends number ? 'number' :
T extends boolean ? 'boolean' :
'other';
function typeName(t: T): TypeName {
return typeof t as TypeName;
}
function test () {}
const a = typeName(1); // number
const b = typeName("s"); // string
const c = typeName(test); // other
//-- Generate type with ReturnType
function createPerson(name: string) {
return {
name: name
}
}
function logPerson(person: ReturnType) {
console.log(person.name)
}
//-- Mapped types
type SuperPoint = {
w: number, x: number, y: number, z: number
}
// Type that creates readonly by making a loop of Item (loop variable)
// in all keys of given type T returning the type of T[Item] and setting it
// to readonly (Readonly type already exists)
type MyReadonly = {
readonly [Item in keyof T]: T[Item]
}
const SuperCenter: MyReadonly = {
w: 0, x: 0, y: 0, z: 0
}
//-- Type modifiers
// with + you can add modifiers, with - remove them
type OneType = {
readonly name: string
}
type WithoutReadonly = {
-readonly [P in keyof T]: T[P]
}
// You could make it even optional with +?
type OneWoRO = WithoutReadonly;
//-- Already defined modifiers
// Partial for allowing not all values
// Required for requiring all the values of a type
// Readonly
// Record
//-- Template literal types
let str: string;
str = "whatever";
let strLiteral: 'hello';
strLiteral = "hello";
// strLiteral = "this is error"
let templateLiteral: `Example: ${string}`;
templateLiteral = "Example: hello";
templateLiteral = "Example: hello this is good"
// templateLiteral = "this is an error Example: ok!"
type CSSValue = number | `${number}px` | `${number}rem`;
type Size = 'small' | 'large';
type Color = 'primary' | 'secondary';
type Style = `${Size}-${Color}`;
function applyStyle(style: Style) {}
applyStyle("small-primary");
//applyStyle("smll-primary");