5.4 KiB
5.4 KiB
TypeScript Conventions
This document outlines our TypeScript conventions for the project.
Configuration
We use strict TypeScript settings to ensure type safety:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
Naming Conventions
Files and Directories
- Use kebab-case for file and directory names:
product-list.tsx
,view-model.ts
- Exception: React components can use PascalCase:
ProductCard.tsx
Variables and Functions
- Use camelCase for variables and functions:
productList
,fetchProducts
- Use PascalCase for React components and types:
ProductList
,ProductDto
- Use ALL_CAPS for constants:
MAX_ITEMS
,API_URL
Effector Naming
- Prefix store names with
$
:$products
,$isLoading
- Suffix effect names with
Fx
:fetchProductsFx
,saveUserFx
- Use descriptive names for events:
setProductFilter
,resetForm
Type Definitions
Interfaces vs. Types
- Use
interface
for object shapes that might be extended - Use
type
for unions, intersections, and simple object shapes
// Interface for extendable objects
interface User {
id: string;
name: string;
}
interface AdminUser extends User {
permissions: string[];
}
// Type for unions and simple objects
type Status = 'idle' | 'loading' | 'success' | 'error';
type ProductFilter = {
category?: string;
minPrice?: number;
maxPrice?: number;
};
API Models
- Use the generated types from the API client
- Create wrapper types when needed for additional properties
import type { productCompositeDto } from '@lib/api/merchant/models/productCompositeDto';
// Extended type with UI-specific properties
type ProductWithUIState = productCompositeDto & {
isSelected: boolean;
isExpanded: boolean;
};
React Component Types
Functional Components
import React from 'react';
interface ButtonProps {
label: string;
onClick: () => void;
disabled?: boolean;
}
export const Button: React.FC<ButtonProps> = ({
label,
onClick,
disabled = false
}) => {
return (
<button
onClick={onClick}
disabled={disabled}
className="btn"
>
{label}
</button>
);
};
Event Handlers
// Form event
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
// ...
};
// Input change
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
// ...
};
// Click event
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
// ...
};
Effector Types
Events
import { createEvent } from 'effector';
// Event with no payload
export const resetFilter = createEvent();
// Event with payload
export const setSearchQuery = createEvent<string>();
// Event with complex payload
export const updateFilter = createEvent<{
category?: string;
minPrice?: number;
maxPrice?: number;
}>();
Stores
import { createStore } from 'effector';
// Define the store state type
interface FilterState {
searchQuery: string;
category: string | null;
minPrice: number | null;
maxPrice: number | null;
}
// Initial state
const initialState: FilterState = {
searchQuery: '',
category: null,
minPrice: null,
maxPrice: null
};
// Create the store
export const $filter = createStore<FilterState>(initialState);
Effects
import { createEffect } from 'effector';
import { ProductsViewsService } from '@lib/api/merchant/services/ProductsViewsService';
import type { productQueryRequestDto } from '@lib/api/merchant/models/productQueryRequestDto';
import type { productCompositeDto } from '@lib/api/merchant/models/productCompositeDto';
// Define effect with explicit parameter and return types
export const fetchProductsFx = createEffect<
productQueryRequestDto,
productCompositeDto[],
Error
>(async (filter) => {
const response = await ProductsViewsService.postApiProductCompositeQueryByCanonical({
requestBody: filter
});
return Array.isArray(response) ? response : [response];
});
Best Practices
-
Avoid
any
: Never use theany
type. Useunknown
if the type is truly unknown. -
Use type inference: Let TypeScript infer types when possible, but add explicit types for function parameters and return values.
-
Null vs. undefined: Use
undefined
for optional values,null
for intentionally absent values. -
Non-null assertion: Avoid using the non-null assertion operator (
!
) when possible. Use optional chaining (?.
) and nullish coalescing (??
) instead. -
Type guards: Use type guards to narrow types:
function isProduct(item: unknown): item is productCompositeDto {
return (
typeof item === 'object' &&
item !== null &&
'id' in item
);
}
- Readonly: Use
readonly
for immutable properties:
interface Config {
readonly apiUrl: string;
readonly timeout: number;
}
- Generics: Use generics for reusable components and functions:
function fetchData<T>(url: string): Promise<T> {
return fetch(url).then(res => res.json());
}
const data = await fetchData<User[]>('/api/users');