- Explain which checks the
strictmode turns on - Get exact literal types from values with
as const - Compare
satisfieswith a type annotation and withas - Use
@typespackages and write a simple.d.tsfile
You already know types, functions, interfaces, unions, classes and generics. This last lesson is about four tools professionals use every day: strict mode, which catches most mistakes; as const, for getting exact types from values; satisfies, which checks a type without widening it; and declaration files, which introduce untyped JavaScript libraries to TypeScript.
Strict mode
"strict": true in tsconfig.json is not one option but a whole set of checks. Without it TypeScript lets many things slide, and the most common JavaScript bugs slip through. These are the most important members of the set:
strictNullChecks:nullandundefinedare separate types; a value that is “possiblyundefined” can't be used without a check.noImplicitAny: a parameter whose type can't be worked out doesn't silently becomeany— you get an error.strictPropertyInitialization: every class field must get a value in its declaration or in the constructor.useUnknownInCatchVariables: incatch (error),errorhas the typeunknownrather thanany.
const users = [
{ name: 'Aysel', age: 15 },
{ name: 'Murad', age: 17 },
];
const found = users.find((u) => u.name === 'Leyla');
console.log(found.age);users.ts:7:13 - error TS18048: 'found' is possibly 'undefined'.
7 console.log(found.age);
~~~~~find may find nothing. Without strict mode the code would compile cleanly and then crash at runtime with TypeError: Cannot read properties of undefined (reading 'age').const users = [
{ name: 'Aysel', age: 15 },
{ name: 'Murad', age: 17 },
];
const found = users.find((u) => u.name === 'Leyla');
console.log(found?.age ?? 'not found');not found
?. and ??.as const
as const asks TypeScript to treat a value as “frozen”: strings become exact literal types instead of string, an array becomes a read-only tuple, and an object's properties become readonly. This is ideal for fixed lists such as roles, statuses and settings, and it neatly replaces the enum we talked about earlier:
const ROLES = ['admin', 'editor', 'viewer'] as const;
type Role = (typeof ROLES)[number];
function canEdit(role: Role): boolean {
return role !== 'viewer';
}
const Status = { Active: 'active', Blocked: 'blocked' } as const;
type Status = (typeof Status)[keyof typeof Status];
const s: Status = Status.Blocked;
console.log(ROLES.join(', '), canEdit('editor'), s);admin, editor, viewer true blocked
Role type is 'admin' | 'editor' | 'viewer', and the Status type is 'active' | 'blocked'. The list also exists in the program, so you can print it and loop over it.const ROLES = ['admin', 'editor', 'viewer'] as const;
ROLES.push('guest');roles.ts:2:7 - error TS2339: Property 'push' does not exist on type 'readonly ["admin", "editor", "viewer"]'.
2 ROLES.push('guest');
~~~~as const turns the array into a read-only tuple, so it has no push method.The satisfies operator
A type annotation (const x: T = ...) checks the value but widens the variable's type to T, and exact information about the value is lost. satisfies T only checks: the value must fit T, while the variable keeps the precise type that TypeScript inferred. In a colour palette some colours are strings and some are tuples of three numbers:
type ColorName = 'red' | 'green' | 'blue';
type Color = string | [number, number, number];
const palette = {
red: [255, 0, 0],
green: '#00ff00',
blue: [0, 0, 255],
} satisfies Record<ColorName, Color>;
console.log(palette.green.toUpperCase());
console.log(palette.red[0]);#00FF00 255
green is a string and red is a tuple, because satisfies keeps the precise type.type ColorName = 'red' | 'green' | 'blue';
type Color = string | [number, number, number];
const annotated: Record<ColorName, Color> = {
red: [255, 0, 0],
green: '#00ff00',
blue: [0, 0, 255],
};
annotated.green.toUpperCase();
const checked = {
red: [255, 0, 0],
green: '#00ff00',
bleu: [0, 0, 255],
} satisfies Record<ColorName, Color>;palette.ts:9:17 - error TS2339: Property 'toUpperCase' does not exist on type 'Color'.
Property 'toUpperCase' does not exist on type '[number, number, number]'.
9 annotated.green.toUpperCase();
~~~~~~~~~~~
palette.ts:14:3 - error TS2353: Object literal may only specify known properties, and 'bleu' does not exist in type 'Record<ColorName, Color>'.
14 bleu: [0, 0, 255],
~~~~green is just a Color. Second error: satisfies still checks the value and catches the typo bleu.| Syntax | Checks? | Variable's type |
|---|---|---|
const x: T = value | yes | T (widened) |
const x = value satisfies T | yes | the value's precise type |
const x = value as T | hardly at all | T |
Declaration files and third-party libraries
A file that contains only types and no executable code. It is a “manual” for JavaScript code, written for TypeScript: which functions exist, which parameters they take and what they return.
Types for npm packages arrive in one of three ways. Many modern packages ship their own .d.ts files, and there is nothing to do. For others, the community publishes types in separate @types/... packages. If neither exists, you write the declaration file yourself.
npm install lodash
npm install --save-dev @types/lodashdependencies, and its types go into devDependencies because they are needed only during development.Suppose the project uses an old library called old-slug that has no types — it turns text into a short name for a URL. When you import it, TypeScript warns that it can't find the module's types:
src/index.ts:1:21 - error TS7016: Could not find a declaration file for module 'old-slug'. '/home/leyla/blog/node_modules/old-slug/index.js' implicitly has an 'any' type.
Try `npm i --save-dev @types/old-slug` if it exists or add a new declaration (.d.ts) file containing `declare module 'old-slug';`
1 import slugify from 'old-slug';
~~~~~~~~~~If there is no @types/old-slug package, we create the file types/old-slug.d.ts and describe the module with declare module. The word declare says that this code already exists elsewhere; we are only stating its type:
declare module 'old-slug' {
export default function slugify(text: string, separator?: string): string;
}types/old-slug.d.ts: a function signature with no body, types only.import slugify from 'old-slug';
const title = 'Hello from Baku';
console.log(slugify(title));
console.log(slugify(title, '_'));hello-from-baku hello_from_baku
src/index.ts now type-checks cleanly, the editor suggests the parameters, and a call like slugify(42) would give error TS2345.When you publish your own library, you don't have to write .d.ts files by hand. With the option "declaration": true in tsconfig.json, tsc creates a .d.ts file next to every .js file:
export interface User {
name: string;
age: number;
}
export declare function greet(user: User): string;greet.d.ts generated from greet.ts: the interface and the function's signature remain, while the body lives in greet.js.Key points
strict: trueturns onstrictNullChecks,noImplicitAnyand other checks; always keep it on.asand!hide the error message but don't fix the problem.as constturns a value into exact literal, read-only types;(typeof LIST)[number]derives a union type from a list.satisfies Tchecks a value againstTbut keeps the variable's precise type..d.tsfiles contain only types; types come from the package itself, from@types/...packages, or from a file you write withdeclare module.
Check yourself
10 questions. Every correct answer earns XP.
"strict": true in tsconfig.json do?