TypeScript eng yaxshi amaliyotlar
Ushbu qo‘llanma toza, qo‘llab-quvvatlash oson va tip jihatidan xavfsiz (type-safe) kod yozishga yordam beradigan asosiy TypeScript eng yaxshi amaliyotlarini qamrab oladi. Ushbu amaliyotlarga rioya qilish kod sifati va dasturchi tajribasini yaxshilaydi.
Loyiha konfiguratsiyasi
Qattiq rejimni yoqing
Maksimal turdagi xavfsizlik uchun har doim tsconfig.json qurilmangizda strict rejimini yoqing:
// tsconfig.json
{
"compilerOptions": {
/* Enable all strict type-checking options */
"strict": true,
/* Additional recommended settings */
"target": "ES2020",
"module": "commonjs",
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
Qattiq tekshiruvlarni yoqing
Kod sifatini yaxshilash uchun ushbu qo‘shimcha qattiq tekshiruvlarni yoqishni o‘ylab ko‘ring:
{
"compilerOptions": {
/* Additional strict checks */
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true
}
}
Tip tizimi bo‘yicha eng yaxshi amaliyotlar
Iloji bo‘lsa, tur xulosasidan foydalaning
Turi topshiriqdan aniq bo‘lsa, TypeScript turlarini chiqarishga ruxsat bering:
// Bad: Redundant type annotation
const name: string = 'John';
// Good: Let TypeScript infer the type
const name = 'John';
// Bad: Redundant return type
function add(a: number, b: number): number {
return a + b;
}
// Good: Let TypeScript infer return type
function add(a: number, b: number) {
return a + b;
}
O‘zingiz sinab ko‘ring »
Aniq turdagi izohlar
Umumiy API turlari va funksiya parametrlari bilan aniq bo‘ling:
// Bad: No type information
function processUser(user) {
return user.name.toUpperCase();
}
// Good: Explicit parameter and return types
interface User {
id: number;
name: string;
email?: string; // Optional property
}
function processUser(user: User): string {
return user.name.toUpperCase();
}
Interfacelar va turdagi taxalluslar
interface va type qachon foydalanishni biling:
// Use interface for object shapes that can be extended/implemented
interface User {
id: number;
name: string;
}
// Extending an interface
interface AdminUser extends User {
permissions: string[];
}
// Use type for unions, tuples, or mapped types
type UserRole = 'admin' | 'editor' | 'viewer';
// Union types
type UserId = number | string;
// Mapped types
type ReadonlyUser = Readonly<User>;
// Tuple types
type Point = [number, number];
O‘zingiz sinab ko‘ring »
any turidan saqlaning
any dan ko‘ra aniqroq turlarga ustunlik bering:
// Bad: Loses type safety
function logValue(value: any) {
console.log(value.toUpperCase()); // No error until runtime
}
// Better: Use generic type parameter
function logValue<T>(value: T) {
console.log(String(value)); // Safer, but still not ideal
}
// Best: Be specific about expected types
function logString(value: string) {
console.log(value.toUpperCase()); // Type-safe
}
// When you need to accept any value but still be type-safe
function logUnknown(value: unknown) {
if (typeof value === 'string') {
console.log(value.toUpperCase());
} else {
console.log(String(value));
}
}
Kod tashkiloti
Modulni tashkil etish
Kodni aniq javobgarlik bilan mantiqiy modullarga ajrating:
// user/user.model.ts
export interface User {
id: string;
name: string;
email: string;
}
// user/user.service.ts
import { User } from './user.model';
export class UserService {
private users: User[] = [];
addUser(user: User) {
this.users.push(user);
}
getUser(id: string): User | undefined {
return this.users.find(user => user.id === id);
}
}
// user/index.ts (barrel file)
export * from './user.model';
export * from './user.service';
Fayl nomlash qoidalari
Doimiy fayl nomlash naqshlariga rioya qiling:
// Good
user.service.ts // Service classes
user.model.ts // Type definitions
user.controller.ts // Controllers
user.component.ts // Components
user.utils.ts // Utility functions
user.test.ts // Test files
// Bad
UserService.ts // Avoid PascalCase for file names
user_service.ts // Avoid snake_case
userService.ts // Avoid camelCase for file names
Eng yaxshi amaliyotlar
- Turlaringiz va interfeyslaringizni hujjatlang.
- Turlar uchun merosdan ko‘ra kompozitsiyani afzal ko‘ring.
tsconfig.jsonqat’iy va dolzarb saqlang.- Kod bazasi rivojlanishi bilan aniqroq turlardan foydalanish uchun refaktor kodi.
Funksiyalar va usullar
Funksiya parametrlari va qaytish turlari
To‘g‘ri parametr va qaytarish turlari bilan aniq va xavfsiz funksiyalarni yozing:
// Bad: No type information
function process(user, notify) {
notify(user.name);
}
// Good: Explicit parameter and return types
function processUser(
user: User,
notify: (message: string) => void
): void {
notify(`Processing user: ${user.name}`);
}
// Use default parameters instead of conditionals
function createUser(
name: string,
role: UserRole = 'viewer',
isActive: boolean = true
): User {
return { name, role, isActive };
}
// Use rest parameters for variable arguments
function sum(...numbers: number[]): number {
return numbers.reduce((total, num) => total + num, 0);
}
O‘zingiz sinab ko‘ring »
Funksiyani ortiqcha ishlatishdan saqlaning
Funksiyaning murakkabligi va mas’uliyatini yodda tuting:
// Bad: Too many responsibilities
function processUserData(userData: any) {
// Validation
if (!userData || !userData.name) throw new Error('Invalid user data');
// Data transformation
const processedData = {
...userData,
name: userData.name.trim(),
createdAt: new Date()
};
// Side effect
saveToDatabase(processedData);
// Notification
sendNotification(processedData.email, 'Profile updated');
return processedData;
}
// Better: Split into smaller, focused functions
function validateUserData(data: unknown): UserData {
if (!data || typeof data !== 'object') {
throw new Error('Invalid user data');
}
return data as UserData;
}
function processUserData(userData: UserData): ProcessedUserData {
return {
...userData,
name: userData.name.trim(),
createdAt: new Date()
};
}
async/await naqshlari
To‘g‘ri async/await foydalanish
Xatolarni to‘g‘ri boshqarish bilan asinxron operatsiyalarni samarali boshqaring:
// Bad: Not handling errors
async function fetchData() {
const response = await fetch('/api/data');
return response.json();
}
// Good: Proper error handling
async function fetchData<T>(url: string): Promise<T> {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json() as T;
} catch (error) {
console.error('Failed to fetch data:', error);
throw error; // Re-throw to allow caller to handle
}
}
// Better: Use Promise.all for parallel operations
async function fetchMultipleData<T>(urls: string[]): Promise<T[]> {
try {
const promises = urls.map(url => fetchData<T>(url));
return await Promise.all(promises);
} catch (error) {
console.error('One or more requests failed:', error);
throw error;
}
}
// Example usage
interface User {
id: string;
name: string;
email: string;
}
// Fetch user data with proper typing
async function getUserData(userId: string): Promise<User> {
return fetchData<User>(`/api/users/${userId}`);
}
Nested async/await’dan saqlaning
callback-lardan ortiqcha foydalanishning oldini olish uchun async/await kodini tekislang:
// Bad: Nested async/await (callback hell)
async function processUser(userId: string) {
const user = await getUser(userId);
if (user) {
const orders = await getOrders(user.id);
if (orders.length > 0) {
const latestOrder = orders[0];
const items = await getOrderItems(latestOrder.id);
return { user, latestOrder, items };
}
}
return null;
}
// Better: Flatten the async/await chain
async function processUser(userId: string) {
const user = await getUser(userId);
if (!user) return null;
const orders = await getOrders(user.id);
if (orders.length === 0) return { user, latestOrder: null, items: [] };
const latestOrder = orders[0];
const items = await getOrderItems(latestOrder.id);
return { user, latestOrder, items };
}
// Best: Use Promise.all for independent async operations
async function processUser(userId: string) {
const [user, orders] = await Promise.all([
getUser(userId),
getOrders(userId)
]);
if (!user) return null;
if (orders.length === 0) return { user, latestOrder: null, items: [] };
const latestOrder = orders[0];
const items = await getOrderItems(latestOrder.id);
return { user, latestOrder, items };
}
Sinov va sifat
Sinov kodini yozish
Bog‘liqlik in’ektsiyasi va sof funksiyalardan foydalangan holda kodingizni sinovdan o‘tkazishni hisobga olgan holda loyihalashtiring:
// Bad: Hard to test due to direct dependencies
class PaymentProcessor {
async processPayment(amount: number) {
const paymentGateway = new PaymentGateway();
return paymentGateway.charge(amount);
}
}
// Better: Use dependency injection
interface PaymentGateway {
charge(amount: number): Promise<boolean>;
}
class PaymentProcessor {
constructor(private paymentGateway: PaymentGateway) {}
async processPayment(amount: number): Promise<boolean> {
if (amount <= 0) {
throw new Error('Amount must be greater than zero');
}
return this.paymentGateway.charge(amount);
}
}
// Test example with Jest
describe('PaymentProcessor', () => {
let processor: PaymentProcessor;
let mockGateway: jest.Mocked<PaymentGateway>;
beforeEach(() => {
mockGateway = {
charge: jest.fn()
};
processor = new PaymentProcessor(mockGateway);
});
it('should process a valid payment', async () => {
mockGateway.charge.mockResolvedValue(true);
const result = await processor.processPayment(100);
expect(result).toBe(true);
expect(mockGateway.charge).toHaveBeenCalledWith(100);
});
it('should throw for invalid amount', async () => {
await expect(processor.processPayment(-50))
.rejects
toThrow('Amount must be greater than zero');
});
});
Tur sinovlari
Turlarni tasdiqlash va yordam dasturlari yordamida kutilganidek ishlashiga ishonch hosil qilish uchun turlaringizni sinab ko‘ring:
// Using @ts-expect-error to test for type errors
// @ts-expect-error - Should not allow negative values
const invalidUser: User = { id: -1, name: 'Test' };
// Using type assertions in tests
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== 'string') {
throw new Error('Not a string');
}
}
// Using utility types for testing
type IsString<T> = T extends string ? true : false;
type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false
// Using tsd for type testing (install with: npm install --save-dev tsd)
/*
import { expectType } from 'tsd';
const user = { id: 1, name: 'John' };
expectType<{ id: number; name: string }>(user);
expectType<string>(user.name);
*/
Ishlash bo‘yicha mulohazalar
Faqat turdagi import va eksport
To‘plam hajmini kamaytirish va daraxt silkinishini yaxshilash uchun faqat turdagi import va eksportlardan foydalaning:
// Bad: Imports both type and value
import { User, fetchUser } from './api';
// Good: Separate type and value imports
import type { User } from './api';
import { fetchUser } from './api';
// Even better: Use type-only imports when possible
import type { User, UserSettings } from './types';
// Type-only export
export type { User };
// Runtime export
export { fetchUser };
// In tsconfig.json, enable "isolatedModules": true
// to ensure type-only imports are properly handled
Haddan tashqari turdagi murakkablikdan saqlaning
Kompilyatsiya vaqtiga ta’sir qilishi mumkin bo‘lgan murakkab turlarga e’tibor bering:
// Bad: Deeply nested mapped types can be slow
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
// Better: Use built-in utility types when possible
type User = {
id: string;
profile: {
name: string;
email: string;
};
preferences?: {
notifications: boolean;
};
};
// Instead of DeepPartial<User>, use Partial with type assertions
const updateUser = (updates: Partial<User>) => {
// Implementation
};
// For complex types, consider using interfaces
interface UserProfile {
name: string;
email: string;
}
interface UserPreferences {
notifications: boolean;
}
interface User {
id: string;
profile: UserProfile;
preferences?: UserPreferences;
}
Literal turlar uchun const Assertions dan foydalaning
Const tasdiqlari bilan turdagi xulosa va unumdorlikni yaxshilang:
// Without const assertion (wider type)
const colors = ['red', 'green', 'blue'];
// Type: string[]
// With const assertion (narrower, more precise type)
const colors = ['red', 'green', 'blue'] as const;
// Type: readonly ["red", "green", "blue"]
// Extract union type from const array
type Color = typeof colors[number]; // "red" | "green" | "blue"
// Objects with const assertions
const config = {
apiUrl: 'https://api.example.com',
timeout: 5000,
features: ['auth', 'notifications'],
} as const;
// Type is:
// {
// readonly apiUrl: "https://api.example.com";
// readonly timeout: 5000;
// readonly features: readonly ["auth", "notifications"];
// }
O‘zingiz sinab ko‘ring »
Qochish kerak bo‘lgan umumiy xatolar
any turidan ortiqcha foydalanish
"Har qanday" dan foydalanishdan saqlaning, chunki u TypeScript turini tekshirishdan ustun turadi:
// Bad: Loses all type safety
function process(data: any) {
return data.map(item => item.name);
}
// Better: Use generics for type safety
function process<T extends { name: string }>(items: T[]) {
return items.map(item => item.name);
}
// Best: Use specific types when possible
interface User {
name: string;
age: number;
}
function processUsers(users: User[]) {
return users.map(user => user.name);
}
Qattiq rejimdan foydalanmaslik
`tsconfig.json` da har doim qattiq rejimni yoqing:
// tsconfig.json
{
"compilerOptions": {
"strict": true,
/* Additional strictness flags */
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true
}
}
Turi haqidagi xulosaga e’tibor bermaslik
Iloji bo‘lsa, TypeScript turlarini chiqarishga ruxsat bering:
// Redundant type annotation
const name: string = 'John';
// Let TypeScript infer the type
const name = 'John'; // TypeScript knows it's a string
// Redundant return type
function add(a: number, b: number): number {
return a + b;
}
// Let TypeScript infer the return type
function add(a: number, b: number) {
return a + b; // TypeScript infers number
}
Qo‘riqchi turini ishlatmaslik
Turlarni xavfsiz tarzda qisqartirish uchun turdagi himoya vositalaridan foydalaning:
// Without type guard
function process(input: string | number) {
return input.toUpperCase(); // Error: toUpperCase doesn't exist on number
}
// With type guard
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function process(input: string | number) {
if (isString(input)) {
return input.toUpperCase(); // TypeScript knows input is string here
} else {
return input.toFixed(2); // TypeScript knows input is number here
}
}
// Built-in type guards
if (typeof value === 'string') { /* value is string */ }
if (value instanceof Date) { /* value is Date */ }
if ('id' in user) { /* user has id property */ }
O‘zingiz sinab ko‘ring »
null va undefined ishlanmayapti
Har doim potentsial "null" yoki "aniqlanmagan" qiymatlarni ishlating:
// Bad: Potential runtime error
function getLength(str: string | null) {
return str.length; // Error: Object is possibly 'null'
}
// Good: Null check
function getLength(str: string | null) {
if (str === null) return 0;
return str.length;
}
// Better: Use optional chaining and nullish coalescing
function getLength(str: string | null) {
return str?.length ?? 0;
}
// For arrays
const names: string[] | undefined = [];
const count = names?.length ?? 0; // Safely handle undefined
// For object properties
interface User {
profile?: {
name?: string;
};
}
const user: User = {};
const name = user.profile?.name ?? 'Anonymous';
W3Schools Pathfinder
Yutuqlaringizni kuzating – bu bepul!
