Efficient TypeScript
- 2025/2/2
- ブログ
- Comments Off on Efficient TypeScript
TypeScript has become a common tool used by developers to add type safety and hence robustness to their projects. Here are some things you probably didn’t know you can do with it!
この記事の目次
- 1. Template Literal Types for Dynamic Strings
- 2. Infer for Smarter Types in Generics
- 3. Mapped Types for Flexible Data Transformations
- 4. Never and Exhaustiveness Checks
- 5. Augmenting Existing Types with Declaration Merging
- 6. Index Signatures with Advanced Constraints
- 7. Use tsconfig Paths for Cleaner Imports
- 8. Utility Types
1. Template Literal Types for Dynamic Strings
Did you know you can enforce dynamic string patterns with TypeScript? This is especially useful for managing API routes, CSS class names, or other structured string formats.
type ApiEndpoint = `/api/${'users' | 'posts'}/${number}`;
const validEndpoint: ApiEndpoint = '/api/users/123'; // ✅ Valid
const invalidEndpoint: ApiEndpoint = '/api/products/123'; // ❌ Error
This ensures your strings follow the expected structure, reducing runtime errors.
2. Infer for Smarter Types in Generics
Using infer
, you can extract and reuse types dynamically. It’s powerful for creating utility types:
type ReturnTypeOf = T extends (...args: any[]) => infer R ? R : never;
function getUser(): { name: string; age: number } {
return { name: 'Alice', age: 25 };
}
const user: ReturnTypeOf = { name: 'Alice', age: 25 }; // Type is inferred
3. Mapped Types for Flexible Data Transformations
Mapped types let you transform object types elegantly. You can create readonly, optional, or partial versions of types dynamically.
type Readonly = {
readonly [K in keyof T]: T[K];
};
type User = { name: string; age: number };
const readonlyUser: Readonly = { name: 'John', age: 30 };
readonlyUser.age = 31; // ❌ Error: Cannot assign to 'age'
Pair this with utility types like Partial
, Required
, or Pick
for even more flexibility.
4. Never and Exhaustiveness Checks
TypeScript’s never
is often overlooked but can enforce exhaustive checks in switch statements or similar constructs:
type Shape = 'circle' | 'square' | 'triangle';
function getArea(shape: Shape) {
switch (shape) {
case 'circle':
return 'Area of circle';
case 'square':
return 'Area of square';
default:
const _exhaustiveCheck: never = shape; // Error if a new case is added without updating
return _exhaustiveCheck;
}
}
This helps maintain type safety as your code evolves.
5. Augmenting Existing Types with Declaration Merging
TypeScript allows you to extend existing types or modules via declaration merging. This is particularly handy for libraries.
declare module 'express' {
export interface Request {
user?: { id: string; role: string };
}
}
app.use((req, res, next) => {
req.user = { id: '123', role: 'admin' }; // Custom property
next();
});
Declaration merging lets you seamlessly extend third-party libraries without modifying the original type definitions.
6. Index Signatures with Advanced Constraints
Index signatures don’t have to be a free-for-all. You can constrain the keys or values for more specificity.
type StyleRules = {
[key: `--${string}`]: string;
};
const styles: StyleRules = {
'--main-color': 'blue',
'--padding': '10px',
};
This pattern is useful for scenarios like CSS custom properties or dynamic key-value maps.
7. Use tsconfig Paths for Cleaner Imports
Deeply nested imports can clutter your codebase. TypeScript’s paths
option in tsconfig.json
helps:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@components/*": ["src/components/*"]
}
}
}
Now, instead of:
import Button from '../../../components/Button';
You can write:
import Button from '@components/Button';
8. Utility Types
TypeScript ships with several built-in utility types that can save you a ton of effort:
Omit
: Exclude specific keys from a type.Pick
: Include specific keys from a type.Record
: Create a key-value type.
Example:
type User = { id: string; name: string; email: string };
type UserWithoutEmail = Omit<User, 'email'>;
const user: UserWithoutEmail = { id: '1', name: 'Alice' };
These utilities are perfect for adapting types to different contexts.
Mastering these lesser-known features of TypeScript will definitely make your code more readable and maintainable. Thanks for reading!
この情報は役に立ちましたか?
カテゴリー: