Skip to content

Latest commit

 

History

History
372 lines (280 loc) · 6.56 KB

File metadata and controls

372 lines (280 loc) · 6.56 KB

Type Alias and Interfaces



For both, the convention is to use TitleCase. Both, Type Alias and Interfaces support readonly properties (value cannot be changed after initialized)

Differences between Types Alias and Interfaces

Type Alias Supports:

  • Unions (|)
  • Intersections (&)
  • Primitives
  • Shorthand functions
  • Advanced type functions (like conditional types)

Shorthand function example:

type Greet = (s: string) => string;

type Human = {
  name: string,
  greet: Greet
}

const human: Human = {
  name: 'Peter',
  greet: (s) =>  s
}

console.log(
  human.greet(`Hi`)
); // Hi

Interfaces

  • Declaration merging
  • Familiarity (extends)

Type Alias

Is how we name our own types We can declare types in one place and import/export them.

type User = {
  name: string;
  age: number;
}

const users: User[] = [
  { name: 'Peter', age: 33 },
  { name: 'Paul', age: 34 }
];

Implements

type Human = {
  eat(): void
}

class Engineer implements Human {
  eat() {
    console.log('Eating');
  }
}

new Engineer().eat(); // Eating

Utility types

Readonly

(Readonly)

type User = {
  name: string,
  age: number
}

const user: Readonly<User> = {
  name: 'Peter',
  age: 3
}

If you try to modify the value of age, TS will error with:

Cannot assign to 'name' because it is a read-only property.

Required

(Required)

Record

(Record<K,V>)

If we avoid passing either of the properties name or age, TS will error. Example:

Type '{}' is missing the following properties from type '{ name: string; age: number; }': name, age

Example:

type User = Record<string, { name: string, age: number }>

const users: User = {
  'peter': { name: 'Peter', age: 30 },
  'wendy': { name: 'Wendy', age: 31 }
}

Partial

(Partial)

In the followinbg example, we are updating a user just passing the properties that will be used. (This plays pretty well at the time of updating state)

type UserType = {
  name: string,
  age: number
}

// const user: User = {
//   name: 'Peter',
//   age: 30
// }

class User<T> {
  constructor(public currentState: T) {}
    update(nextState: Partial<T>) {
      this.currentState = { ...this.currentState, ...nextState }
    }
}

const user = new User<UserType>({ name:'Peter', age: 30 });

user.update({ age: 31 });

console.log(user);

// [LOG]: User: {
//   "currentState": {
//     "name": "Peter",
//     "age": 31
//   }
// } 

Interfaces

Used to define an object type.

As with Type Alias, we abstract the type annotation, so instead of doing this...

const logUser = (user: { name: string; age: number }): void => {
  // since we are not returning we use void
  console.log(user.name, user.age);
}

... we do this ...

interface User {
  name: string;
  age: number;
  introduce(): string;
} 

const myUser = {
  name: 'Peter',
  age: 35,
  lastname: 'Pan',
  introduce() {
    return `Hi, I'm ${this.name}`;
  }
}

const logUser = (user: User): void => {
  // since we are not returning we use void
  console.log(user.name, user.age); // "Peter",  35 

  console.log(user.introduce()); // "Hi, I'm Peter" 
}

logUser(myUser);

The object we are passing must have ALL the properties declared in our interface (and the right types); we can have more properties than the ones we have in our interface, but not less.

Interfaces are open... We can have multiple declarations in the same scope. TS is going to merge the declarations of the interfaces into a single type (this is called Interface Declaration Merging). This is not supported by Types Alias

interface IHuman {
  eat(): void
}

interface IHuman {
  isEmployed(): boolean
}

So, we can augment existing objects... For example, Window

interface Window {
  someMethod(): number
}

window.someMethod;

Interfaces (as Classes) support optional properties

interface IPerson {
  name: string;
  hobbies?: string[]
}

const person: IPerson = {
  name: 'Peter',
}

Extends

Interfaces can extend from other interfaces (like classes)

In the following example, TS is going to require that engineer has both methods eat() and isEmployed() returning the proper value type.

interface Human {
  eat(): void
}

interface Engineer extends Human {
  isEmployed(): boolean
}

const engineer: Engineer = {
  eat: () => console.log('Eating'),
  isEmployed: () => true
}

Implements

Everything we have on the interface should be implemented in the class.

interface IHuman {
  eat(): void
}

interface IEngineer extends IHuman {
  isEmployed(): boolean
}

class Engineer implements IEngineer {
  eat() {
    console.log('Eating');
  }

  isEmployed() {
    return true;
  }
}

We can implement as many interfaces as we want

class myClass implements IOne, ITwo { }

Read only property example

In the following example, we are stating that myUser is going to hold a an object with 2 properties: name (which should be immutable) and age. If we try to change the value of name, TS will error: Cannot assign to 'name' because it is a read-only property.

interface IUser {
  readonly name: string;
  age: number;
} 

const myUser: IUser = {
  name: 'Peter',
  age: 35
}

myUser.name = 'Wendy';

Another example but with a class. In this case, we first tell the compiler that user is going to hold a type of IUser Then we initialize a new User. Then we try to mutate the value of the property name. TS will complain as before: Cannot assign to 'name' because it is a read-only property.

interface IUser {
  readonly name: string;
  age: number;
} 

class User implements IUser {
  name: string;
  age: number;

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

let user: IUser;
user = new User('Peter', 33);


user.name = 'Wendy';