Note: A satisfies operator may improve this issue. A detailed discussion of this is here: microsoft/TypeScript#47920
Using as in TypeScript is usually bad, since it can downcast your type. Example:
type Dog = { name: string; breed: string };
const puppy = { name: 'Spot' } as Dog;
// Uh oh, runtime error! `puppy.breed` is undefined.
console.log(puppy.breed.toUpperCase());With { name: 'Spot' } as Dog, TypeScript allows you to downcast to Dog even though it is missing the breed property. This is by design: using an as assertion is an escape hatch that tells TypeScript you know what you're doing. The compiler will do its best to prevent impossible assertions, such as const myString = 123 as string;, but downcasts are fair game.
Here's the better way:
type Dog = { name: string; breed: string };
// TS Error: Property 'breed' is missing in type '{ name: string; }' but required in type 'Dog'
const puppy: Dog = { name: 'Spot' };This is an improvement; we prefer compiler errors over runtime errors.
Using const puppy: Dog = { name: 'Spot' } leverages a type annotation. This strictly enforces the type, and also has the benefit here of enforcing freshness.
I see as being used commonly when returning values from a function:
function puppyTime() {
return { name: 'Spot', breed: 'Cute' } as Dog;
}This suffers from the drawbacks mentioned above. This is problematic when the type of Dog changes in the future. If we were to add age: number to Dog someday, puppyTime() would return an incomplete value and potentially cause runtime errors.
Instead, use a return value annotation:
function puppyTime(): Dog {
return { name: 'Spot', breed: 'Cute' };
}This will safeguard your code for future type changes.
Here's some examples for map and reduce. Other utilities should offer you generics to allow for function return inference. If not, you can annotate the return value yourself instead of as assertion.
const numbers = [1, 2, 3];
// Don't do this:
numbers.map(num => {
return { name: `Puppy #${num}`, breed: 'V cute' } as Dog;
});
// Do this:
numbers.map<Dog>(num => {
return { name: `Puppy #${num}`, breed: 'V cute' };
});
// Don't do this:
numbers.reduce((acc, cur) => {
return { ...acc, [`dog-${cur}}`]: { name: `Puppy #${cur}`, breed: 'V cute' } } as Record<string, Dog>;
}, {});
// Do this:
numbers.reduce<Record<string, Dog>>((acc, cur) => {
return { ...acc, [`dog-${cur}}`]: { name: `Puppy #${cur}`, breed: 'V cute' } };
}, {});Thank you for coming to my TED talk.