Charly POLY - Senior Software Engineer at
- writing TypeScript EssentialsÂ
  series on
"static vs runtime limit":
- any to a "defined type"
- "as" keyword
const chat: Chat = await http.post(
`/chats`,
{ id }
).then(
response => response.body.chat as Chat
);
// ...
export const Form = reduxForm<any, any>(
/* ... */
)
A type guard is some expression that performs a runtime check that guarantees the type in some scope.Â
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.
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
Example with redux actions
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;
}
}
“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;
}
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