We can rely on the tsc
to infer types. We can also statically type a variable to be a Union Type
:
type ExampleOfUnionType = string | boolean | number
This gets tricky when you introduce objects. The solution is to utilize type guards
like the typeof
and instanceof
operators. This is the same thing we would do without TypeScript, but not doing it will result in an error.
type UserName = string | { name: string }
const logName = (user: UserName) => {
if (typeof user === 'string') {
console.log(user);
}
else {
console.log(user.name)
}
}
logName('Max');
logName({ name: 'Maxwell Kendall' });
Union types are especially useful when defining an enum
:
type ReformedAndConfessionalDenominations = 'PCA' | 'OPC' | 'URC';
// Error:
const firstBaptist: ReformedAndConfessionalDenominations = 'SBC';
Some peices of a type might be generic enough to abstract into an interface that may be present on multiple types.
Futhermore, interfaces may be composed together to define a type or another interface.
interface x extends y {
test: string
}
Use the as
keyword behind a reference to signal we know what kind of type the thing is.
const func = (a: number | string) => {
// a.toUpperCase() error
(a as string).toUpperCase()
}
If we're not sure what a type is going to be until invocation, we can use a generic type. This will allow ups to enforce some level of validation while deferring the specifics to invocation time:
const getArray = <Item>(item: Item, arr: Item[]): Item[] => {
return arr.concat(item)
}
// this is good, all strings 👇
getArray('1', ['1']);
// this is bad, mixed types 👇
getArray(1, ['1']);
Generics can be constrained via the extends
keyword like so:
interface Test {
name: string
}
const getArray = <Item extends Test>(item: Item, arr: Item[]): Item[] => {
return arr.concat(item)
}
// this is good, all strings 👇
getArray(
{ name: 'max', age: 12 },
[{ name: 'claire', age: 4 }]
);
This allows for a bit more definition to our type setting.