TypeScript type-guards 💂‍♂️
#whoami
Charly POLY - Senior Software Engineer at
- writing TypeScript EssentialsÂ
  series on
Plan
- "Types are not real": static vs runtime
- TypeScript embed type-guards
- The Discriminated Unions
- User-Defined type-guards
- How to start?
"Types aren't real"
Make "types" stronger
"static vs runtime limit":
- any to a "defined type"
- "as" keyword
Typing "weak spots"
const chat: Chat = await http.post(
`/chats`,
{ id }
).then(
response => response.body.chat as Chat
);
// ...
export const Form = reduxForm<any, any>(
/* ... */
)
"Make types reals"
A type guard is some expression that performs a runtime check that guarantees the type in some scope.Â
TypeScript embed type-guards
typeof type-guards
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.
TypeScript embed type-guards
Embed type-guards:
- typeof operator
- instanceof operator
- in operator
The Discriminated Unions
Discriminated Unions is a pattern that allow to build types that shares a common property but have different shapes
- Types that have a common, singleton type property — the discriminant.
- Then, a type alias that takes the union of those types — the union.
- Finally, a type guard on the common property (on the discriminant).
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
The Discriminated Unions
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;
}
}
The Discriminated Unions
User-defined type-guards
“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.
User-defined type-guards
function isFish(pet: any): pet is Fish {
return pet.swim !== undefined;
}
- guard function argument type, like for overloads, should be as open as possible.
 - a new is operator, called type predicate.
User-defined type-guards
User-defined type-guards
Good points of User-Defined type-guards:
- matches real-world expectations
- more flexible
- support complex types
- stateless and isolated ➡️ testable
Where to start?
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
Where to start?
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
Where to start?
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
Conclusion
- Types can be real
- Avoid "as" operator, use type-guards
- TypeScript is more powerful than you think
Thanks for listening!
For more, look at the "TypeScript — Make types “real”, the type guards" article
TypeScript - type-guards
By Charly Poly
TypeScript - type-guards
- 1,534