There are so many advantages of TypeScript than JavaScript.
In this article, I will introduce my love-to-use TypeScript use cases.
Index
- Union Types
-
any
Type - Overloads are Awesome
Union Types
Flexible String Union Types
// accept all strings
const name: string = 'anyone';
// stricted to string "john"
const john: 'john' = 'someone'; // type error
// union type (only "john" and "michael" are accepted)
const men: 'john' | 'michael' = 'smith'; // type error
Flexible Union Interfaces
interface OptionA {
value: 'A';
funcOnlyOptionA: () => void;
}
interface OptionOthers {
value: Omit<string, 'A'>;
funcOptionOthers: () => void;
}
Omit<T, U>
is a TypeScript built-in type and it excludes U
from T
.
// no error
const option1: OptionA | OptionOthers = {
value: 'A',
funcOnlyOptionA: () => console.log('Hello Interface A'),
};
// no error
const option2: OptionA | OptionOthers = {
value: 'B',
funcOptionOthers: () => console.log('Hello String (without "A")'),
};
// NO ERROR!
// because this is a `union` type
const option3: OptionA | OptionOthers = {
value: 'A',
funcOptionOthers: () => console.log('Hello String (without "A")'),
};
But, how to compare option1
and option2
?
In other words, how to get type errors in the case of option3
?
Let's get into deeper:
const isOptionA = (option: any): option is OptionA => {
return (option as OptionA).value === 'A';
};
const option: OptionA | OptionOthers = generateOption();
if (isOptionA(option)) {
// now `option` is casted to `OptionA` automatically
// good
option.funcOptionA();
// type error!
option.funcOptionOthers();
}
any
Type
Naturally, Types are Checked Automatically
interface User {
name: string;
}
const user: User = { name: 'John' };
const updatedUser: User = {
...user,
name: 'New John', // no errors
bio: 'I am something!', // type error!
};
// but if we use `any` type, we can't get the benefit above:
const person = { name: 'Alex' }; // `person` is `any` type
const updatedPerson = {
...person,
name: 'AlexxX', // no errors
bio: 'I am a hacker', // no errors!
};
any
Type can Convert ANY Types
See a simple example here:
interface User {
name: string;
}
const possiblyUserObject: any = {};
const typedUser: User = possiblyUserObject; // no errors
console.log(typedUser.name); // undefined
It seems natural enough and there is nothing to study. lol
Anything else? Sure.
We can Convert JSON Object to the User Defined Types
Consider that we will fetch user data from a server. We may define the http client like this way:
(by using a http library named ky)
import ky from 'ky-universal';
const prefixUrl: string = 'http://localhost';
export class Http {
public async get(url: string) {
return ky.get(url, { prefixUrl }).json();
}
public async post(url: string, data?: any) {
return ky.post(url, { prefixUrl, json: data }).json();
}
public async put(url: string, data?: any) {
return ky.put(url, { prefixUrl, json: data }).json();
}
public async delete(url: string) {
return ky.delete(url, { prefixUrl }).json();
}
}
And we can use this Http
class like below:
const http = new Http();
const user = await http.get('api/user/1');
console.log(user.name); // no errors, because `user` is `any` type
Yes it works perfectly, but we can't get benefit from TypeScript.
So let's rewrite Http
class with TypeScript generics:
import ky from 'ky-universal';
const prefixUrl: string = 'http://localhost';
export class Http {
// because json is `any` type, so we can cast it to the generic type `T`
public async get<T>(url: string): Promise<T> {
return ky.get(url, { prefixUrl }).json();
}
public async post<T>(url: string, data?: any): Promise<T> {
return ky.post(url, { prefixUrl, json: data }).json();
}
public async put<T>(url: string, data?: any): Promise<T> {
return ky.put(url, { prefixUrl, json: data }).json();
}
public async delete<T>(url: string): Promise<T> {
return ky.delete(url, { prefixUrl }).json();
}
}
And finally we can get intellisense (code complete, auto correct and so on) from your IDE like editors such as VSCode:
const http = new Http();
const user = await http.get<User>('api/user/1');
// or:
// const user: User = await http.get('api/user/1'); // no errors!
console.log(user.name); // this user is the type `User`!
Overloads are Awesome
JavaScript can't create overloads as far as I know.
If we fetch user data by id
or ids
, the source code is something like this:
// JavaScript
function fetchData(id) {
if (Array.isArray(id)) {
return [
{ name: 'john' },
{ name: 'alex' },
];
}
return { name: 'john' };
}
const data1 = fetchData(1);
console.log(data1.name); // john
const data2 = fetchData([1, 2]);
console.log(data2[0].name); // john
// but, if `id` is object, we must check the return data is array or not:
const data3 = fetchData(idOrIds);
if (Array.isArray(data3)) {
// data3 is users
} else {
// data3 is user
}
On the other hand, it is possible to create overloads in TypeScript:
// define overloads!
function fetchData(id: number): User
function fetchData(ids: number[]): User[]
// implementation!
function fetchData(id: number | number[]): User | User[] {
if (Array.isArray(id)) {
return [
{ name: 'john' },
{ name: 'alex' },
];
} else {
return { name: 'john' };
}
}
const data1 = fetchData(1);
console.log(data1.name); // john (no errors! awesome!)
const data2 = fetchData([1, 2]);
console.log(data2[0].name); // john (no errors! awesome!)
Top comments (1)
I could not understand the overload example.
I'm confused that there is only one implementation of "fetchData". Also, what is the need to define the function twice? Can't we just write the implementation part and call that function with different types of data?