Typescript cơ bản cho người mới phần 4

Như đã nói ở bài typescript số 3. Ở bài này chúng ta sẽ đi vào tìm hiểu sự giống và khác nhau giữa interfacetype

Primitive types

Primitive types là những type có sẵn trong Typescript như là number, string, boolean, null, undefined. Các bạn có thể khai báo như này với type nhưng với interface thì không.

type School = string;

Chúng ta có thể sử dụng union cho type nữa như sau.

type Score = string | number;

Vì thế khi các bạn muốn định nghĩa các primitives type thì sẽ sử dụng type nhé.

Union types

Chúng ta đã học về union types ở phần 1 và để sử dụng nó thì rất đơn giản với type. Nhưng với interface thì không được. Tuy nhiên chúng ta có thể tạo ra union type từ 2 hoặc nhiều interface

type Transport = 'Bus' | 'Car' | 'Bike' | 'Walk';
interface CarBattery {
  power: number;
}
interface Engine {
  model: string;
}
type HybridCar = Engine | CarBattery;

Function types

Cả interface và type đều có thể khai báo function types nhưng cách khai báo sẽ hơi khác một chút, type thì sẽ dùng dấu => còn interface là dấu 2 chấm :

Theo kinh nghiệm của mình thì khi khai báo function types thì nên sử dụng type sẽ trông nó rõ ràng và ngắn gọn hơn.

type AddFn =  (num1: number, num2:number) => number;
interface IAdd {
   (num1: number, num2:number): number;
}

Khi function trong typescript trở nên phức tạp hơn khi kết hợp với conditional types, mapped types thì việc làm với type sẽ trở nên dễ dàng hơn nhiều với interface. Conditional types và mapped types là gì thì mình sẽ chia sẻ cho các bạn ở những phần tới nhé.

type Car = 'ICE' | 'EV';
type ChargeEV = (kws: number)=> void;
type FillPetrol = (type: string, liters: number) => void;
type RefillHandler<A extends Car> = A extends 'ICE' ? FillPetrol : A extends 'EV' ? ChargeEV : never;

Declaration merging

Một điểm khác biệt rõ rệt nhất đó chính là ở interface, các bạn có thể khai báo trùng tên, còn ở type thì hoàn toàn không. Ở interface khi khai báo trùng tên thì nó sẽ gộp các thuộc tính lại với nhau như đoạn code ở dưới mình có thể sử dụng cả nameage

interface Client { 
    name: string; 
}

interface Client {
    age: number;
}

const harry: Client = {
    name: 'Harry',
    age: 41
}
type Client { 
    name: string; 
}

type Client {
    age: number;
}
// ❌ Duplicate identifier 'Client'.ts(2300)

const harry: Client = {
    name: 'Harry',
    age: 41
}

Extends vs intersection

Interface có thể kế thừa một hoặc nhiều interface khác bằng việc sử dụng từ khóa extends, để kế thừa toàn bộ các thuộc tính.

interface ITakeSkip {
  take: number;
  skip: number;
}
interface IFilter extends ITakeSkip {
  filter: string;
}
const filter : IFilter = {
  filter: "",
  take: 5,
  skip: 5
}

Như đoạn code trên thì interface IFilter kế thừa interface ITakeSkip, khi sử dụng có thể sử dụng các thuộc tính như filter, takeskip luôn. Tương tự cho type thì chúng ta sẽ sử dụng Intersection như sau:

type ITakeSkip = {
  take: number;
  skip: number;
}
type IFilter = {filter: string} & ITakeSkip;
const filter : IFilter = {
  filter: "",
  take: 5,
  skip: 5
}

Một lưu ý quan trọng đó chính là khi sử dụng union types. Thì không thể sử dụng từ khóa extends từ interface đối với union types được

type Jobs = 'salary worker' | 'retired';
// An interface can only extend an object type or intersection of object types with statically known members.
interface MoreJobs extends Jobs {
  description: string;
}

Type thì có thể kế thừa từ interface thông qua intersection như này:

interface Client {
    name: string;
}
type VIPClient = Client & { benefits: string[]};

Khi sử dụng extends trong interface nếu có thuộc tính trùng nhau thì sẽ báo lỗi ngay lập tức các bạn nhé.

interface Person {
  getPermission: () => string;
}
// Interface 'Staff' incorrectly extends interface 'Person'.
// The types returned by 'getPermission()' are incompatible between these types.
//Type 'string[]' is not assignable to type 'string'.
interface Staff extends Person {
   getPermission: () => string[];
}

Tuy nhiên khi sử dụng với từ khóa type thì lại khác, nó không báo lỗi mà lại gộp 2 thằng lại luôn như sau. Tuy sử dụng không lỗi nhưng mình khuyến khích các bạn không nên khai báo thuộc tính hay phương thức trùng tên khi sử dụng kế thừa, đôi khi xảy ra nhiều lỗi không mong muốn.

type Person = {
  getPermission: (id: string) => string;
};

type Staff = Person & {
   getPermission: (id: string[]) => string[];
};

const AdminStaff: Staff = {
  getPermission: (id: string | string[]) =>{
    return (typeof id === 'string'?  'admin' : ['admin']) as string[] & string;
  }
}

Tóm lại thì theo kinh nghiệm làm việc của mình thì mình thường sử dụng từ khóa type hơn là interface, tuy nhiên mình cũng có sử dụng interface trong một số trường hợp mà muốn kế thừa như ví dụ mình làm ban đầu khi mà khai báo các params cho việc sàng lọc dữ liệu. Việc chọn type hay là interface là tùy vào mỗi người, nhưng chúng ta đã phân biệt được sự khác nhau giữa chúng để từ đó có thể sử dụng cho chính xác và tốt hơn.

Bài viết có tham khảo từ blog.logrocket.com

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments