unknown
is safe version of any
.
We should consider unknowon
first then any
.
When would like to take multiple type as argument.
function isNumeric(target: string | number | null) {
if (target === null) {
return false;
}
if (typeof target === 'number') {
return true;
}
else if (typeof target === 'string') {
// super cool logic to detect if the text is numerical
}
}
When you took union types, you would like to detect the value is exactly what of those. (like pattern match)
For undefined and null, you can just compare like x === null
.
For native type, you can use typeof
for type guarding.
You can see the example for those in above.
For the types you create, you have to write a specific function for that.
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as any as Fish).swim !== undefined;
}
Be aware, you can be wrong when writing type guard function.
TIP:
In TypeScript definition, Array.prototype.filter
supports type guard. So you can use it to filter union typed array.
const fishAndBirds = [/* ... */];
const fish = fishAndBirds.filter(isFish); // you can also write directly `fishAndBirds.filter(forb: forb is Fish => ...`
Type guard is also useful for union typed object. It means you can use it for FSA styled redux action. 👏
type Actions =
| {type: 'user/create' as const, payload: User}
| {type: 'user/delete' as const, payload: never}
| {type: 'user/create/error' as const, payload: Error};
function reducer(state: State, action: Actions) {
switch (action.type) {
case 'user/create': {
console.log(action.payload.name); // this is vaild. compiler can infer payload is a User
}
case 'user/delete': {
console.log(action.payload.name); // this is going to be invalid because compiler infered payload is never
}
}
}
When you write a function which take object and field to access the object.
function getterSample<T>(target: T, field: keyof T) {
return target[field];
}
const targetA = {a: 12, b: 13};
getterSample(targetA, 'a');
getterSample(targetA, 'c'); // compile error because targetA has no 'c' as a member
Generics type argument can be anything besically. However in case we should expect it is following some definition.
I'm introducing example in the section of Tuple
.
TypeScript compiler allows to get field value from the key of Object.
https://www.typescriptlang.org/docs/handbook/advanced-types.html#index-types
function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] {
return names.map(n => o[n]);
}
If you'd like bring multiple values as Array but the length is fixed, you should use Tuple instead of Array. Actually both of them are same in runtime.
function getTwo<T, F1 extends keyof T, F2 extends keyof T>(target: T, fields: [F1, F2]): [T[F1], T[F2]] {
const [f1, f2] = fields;
return [target[f1], target[f2]] as const;
}
TypeScript allows to declare overload function.
So the getTwo
can be more reusable but statically typed.
function getMultiple<T, F1 extends keyof T>(target: T, fields: [F1]): [T[F1]];
function getMultiple<T, F1 extends keyof T, F2 extends keyof T>(target: T, fields: [F1, F2]): [T[F1], T[F2]];
function getMultiple<T, F1 extends keyof T, F2 extends keyof T, F3 extends keyof T>(target: T, fields: [F1, F2, F3]): [T[F1], T[F2], T[F3]];
// ...
function getMultiple<T, /* implement getMultiple which follows all of typing of above. May be you can use any. */ {
return foo;
}