Item 9: Prefer Type Annotations to Type Assertions
May 10, 2024 ยท View on GitHub
Things to Remember
- Prefer type annotations (
: Type) to type assertions (as Type). - Know how to annotate the return type of an arrow function.
- Use type assertions and non-null assertions only when you know something about types that TypeScript does not.
- When you use a type assertion, include a comment explaining why it's valid.
Code Samples
interface Person { name: string };
const alice: Person = { name: 'Alice' };
// ^? const alice: Person
const bob = { name: 'Bob' } as Person;
// ^? const bob: Person
const alice: Person = {};
// ~~~~~ Property 'name' is missing in type '{}' but required in type 'Person'
const bob = {} as Person; // No error
const alice: Person = {
name: 'Alice',
occupation: 'TypeScript developer'
// ~~~~~~~~~ Object literal may only specify known properties,
// and 'occupation' does not exist in type 'Person'
};
const bob = {
name: 'Bob',
occupation: 'JavaScript developer'
} as Person; // No error
const people = ['alice', 'bob', 'jan'].map(name => ({name}));
// { name: string; }[]... but we want Person[]
const people = ['alice', 'bob', 'jan'].map(
name => ({name} as Person)
); // Type is Person[]
const people = ['alice', 'bob', 'jan'].map(name => ({} as Person));
// No error
const people = ['alice', 'bob', 'jan'].map(name => {
const person: Person = {name};
return person
}); // Type is Person[]
const people = ['alice', 'bob', 'jan'].map(
(name): Person => ({name})
); // Type is Person[]
const people: Person[] = ['alice', 'bob', 'jan'].map(name => ({name})); // OK
document.querySelector('#myButton')?.addEventListener('click', e => {
e.currentTarget
// ^? (property) Event.currentTarget: EventTarget | null
// currentTarget is #myButton is a button element
const button = e.currentTarget as HTMLButtonElement;
// ^? const button: HTMLButtonElement
});
const elNull = document.getElementById('foo');
// ^? const elNull: HTMLElement | null
const el = document.getElementById('foo') as HTMLElement;
// ^? const el: HTMLElement
const el = document.getElementById('foo')!;
// ^? const el: HTMLElement
document.getElementById('foo')?.addEventListener('click', () => {
alert('Hi there!');
});
interface Person { name: string; }
const body = document.body;
const el = body as Person;
// ~~~~~~~~~~~~~~
// Conversion of type 'HTMLElement' to type 'Person' may be a mistake because
// neither type sufficiently overlaps with the other. If this was intentional,
// convert the expression to 'unknown' first.
const el = document.body as unknown as Person; // OK