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.