// JavaScript
const a = 10;
const myObject = { a: 1, b: 'string' }
function hello_world(name) {
return `hello ${name}!`;
}
// TypeScript
interface MyObject {
a: number;
b: string;
}
const a: number = 10;
const myObject: MyObject = { a: 1, b: 'string' }
function hello_world(name: string) {
return `hello ${name}!`;
}
const chat: Chat = await http.post(
`/chats`,
{ id }
).then(
response => response.body.chat as Chat
);
// ...
export const Form = reduxForm<any, any>(
/* ... */
)
A type-guards is an expression that guarantee that a type is valid in a scope
Example: typeof
function formatMoney(amount: string | number): string {
let value = amount; // value type is number or string
if (typeof amount === "string") {
value = parseInt(amount, 10); // amount type is string
}
return value + " $"; // value type is number
}
// calling formatMoney({ myObject: 1} as any)
// will not call parseInt with an object
TypeScript and JavaScript runtime are now tied to the same behaviour.
interface Action { type: string; }
interface ActionA extends Action {
type: 'ActionA';
mypropA: string;
}
interface ActionB extends Action {
type: 'ActionB';
mypropB: string;
}
type anyAction = ActionA | ActionB;
// ...
function reducer(state: State, action: anyAction): Action {
switch(action.type) { // "ActionA" | "ActionB"
case 'ActionA':
return { ...state, prop: action.mypropB }; // TS ERROR!
break;
case 'ActionB':
return { ...state, prop: action.mypropB }; // OK
break;
default:
return state;
}
}
Discriminated Unions is a pattern that allow to build types that shares a common property but have different shapes
interface Action {
type: string; // the discriminant
}
interface ActionA extends Action {
type: 'ActionA';
mypropA: string;
}
interface ActionB extends Action {
type: 'ActionB';
mypropB: string;
}
type anyAction = ActionA | ActionB; // the union
// ...
switch(action.type) { /* ... */ }
“real world usage” of TypeScript is not restricted to scalar types (string, boolean, number, etc…).
Real world applications mainly deals with complex object or custom types.
ÂThis is when “User-Defined Type Guards” help us.
function isFish(pet: any): pet is Fish {
return pet.swim !== undefined;
}
interface Fish {
swim: (number, number, number) => [number, number, number]
}
interface Cat {
walk: (number, number) => [number, number]
}
const isFish : (pet: any) => pet is Fish = pet => !!pet.swim;
const f: Fish = { /* ... */ }
if (isFish(f)) {
// true for TypeScript, and true at runtime
}
Good points of User-Defined type-guards:
Avoid "any" and "as" and use type-guards for complex use-cases
if (!!myobject.someProp) {
(<MyType>myobject).someMethod()
}
➡️ User-Defined type-guards
function reducer(state: State, action: any): Action {
switch(action.type) { // "ActionA" | "ActionB"
case 'ActionA':
return { ...state, prop: <ActionA>action.mypropA };
break;
// ...
default:
return state;
}
}
➡️ Discriminated Unions
Save time while building type-guards by using io-ts
Introduced in  “Typescript and validations at runtime boundaries” article by @lorefnon,
io-ts is an active library that aim to solve the same problem:
TypeScript compatible runtime type system for IO decoding/encoding
io-ts overview
const Person = t.interface({
name: t.string,
age: t.string
})
interface IPerson extends t.TypeOf<typeof Person> {}
// same as
// interface IPerson {
// name: string
// age: number
// }
let a: any = {};
if (Person.is(a)) {
// a is a Person type
}
Powerful API:
Â
- decode()
- encode()
- is()
Â
and also,
- custom error reporters
- unions and recursives types support
For more, look at the "TypeScript — Make types “real”, the type guards" article