Как по коду компонента определяешь что он хорошо читается?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Читаемость кода компонента
Определение качества читаемости компонента требует анализа нескольких факторов. Хороший компонент должен быть понятным с первого взгляда, легко поддерживаемым и тестируемым.
1. Размер компонента
Первый признак плохой читаемости - это размер компонента:
// ❌ Плохо - компонент слишком большой
function UserDashboard({ userId }) {
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
const [comments, setComments] = useState([]);
const [isLoadingUser, setIsLoadingUser] = useState(false);
const [isLoadingPosts, setIsLoadingPosts] = useState(false);
const [isLoadingComments, setIsLoadingComments] = useState(false);
// ... 200 строк кода
return (...) // 150 строк JSX
}
// ✅ Хорошо - разделено на логические части
function UserDashboard({ userId }) {
return (
<div>
<UserInfo userId={userId} />
<UserPosts userId={userId} />
<UserComments userId={userId} />
</div>
);
}
Правило: Компонент должен умещаться на одном экране (50-100 строк). Если больше - требуется рефакторинг.
2. Ясность пропсов
Пропсы должны быть явными и типизированными:
// ❌ Плохо - непонятные пропсы
function Button({ p, c, o, d, s }) {
return <button padding={p} color={c} onClick={o}>{d}</button>;
}
// ✅ Хорошо - явные и типизированные
interface ButtonProps {
padding?: string;
color?: 'primary' | 'secondary';
onClick?: () => void;
disabled?: boolean;
size?: 'small' | 'medium' | 'large';
children: React.ReactNode;
}
function Button({
padding = '1rem',
color = 'primary',
onClick,
disabled = false,
size = 'medium',
children
}: ButtonProps) {
return (
<button
onClick={onClick}
disabled={disabled}
className={`btn btn-${color} btn-${size}`}
style={{ padding }}
>
{children}
</button>
);
}
3. Отделение логики от представления
Компонент хорошо читается, когда логика отделена от UI:
// ❌ Плохо - логика смешана с представлением
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(r => r.json())
.then(data => setUser(data))
.catch(err => console.error(err));
}, [userId]);
if (!user) return <div>Loading...</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
<img src={user.avatar} />
</div>
);
}
// ✅ Хорошо - логика в хуке, представление отделено
function useUser(userId) {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
const loadUser = async () => {
setIsLoading(true);
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
setUser(data);
} catch (err) {
setError(err);
} finally {
setIsLoading(false);
}
};
loadUser();
}, [userId]);
return { user, isLoading, error };
}
function UserProfile({ userId }) {
const { user, isLoading, error } = useUser(userId);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error loading user</div>;
if (!user) return null;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
<img src={user.avatar} alt={user.name} />
</div>
);
}
4. Именование переменных и функций
Имена должны быть описательными и однозначными:
// ❌ Плохо - непонятные имена
function comp({ d, p }) {
const [x, setX] = useState(false);
const y = d.filter(i => i.active);
const z = () => setX(!x);
return <div>{y.map(i => <Item key={i.id} o={i} c={z} />)}</div>;
}
// ✅ Хорошо - описательные имена
function ActiveItemsList({ items, onItemSelect }: ActiveItemsListProps) {
const [isExpanded, setIsExpanded] = useState(false);
const activeItems = items.filter(item => item.isActive);
const toggleExpanded = () => setIsExpanded(!isExpanded);
return (
<ul className="items-list">
{activeItems.map(item => (
<Item
key={item.id}
item={item}
onSelect={onItemSelect}
/>
))}
</ul>
);
}
5. Единственная ответственность (SRP)
Компонент должен иметь одну причину для изменения:
// ❌ Плохо - много ответственности
function Dashboard() {
// Получение данных пользователя
const [user, setUser] = useState(null);
// Получение данных постов
const [posts, setPosts] = useState([]);
// Управление фильтрацией
const [filter, setFilter] = useState('');
// Управление сортировкой
const [sortBy, setSortBy] = useState('date');
// Управление пагинацией
const [page, setPage] = useState(1);
// ... 300 строк кода
}
// ✅ Хорошо - каждый компонент отвечает за одно
function Dashboard() {
return (
<div>
<UserSection />
<PostsSection />
</div>
);
}
function PostsSection() {
return (
<div>
<PostsFilter />
<PostsList />
<PostsPagination />
</div>
);
}
6. Условная логика - просто и понятно
// ❌ Плохо - вложенные условия
function UserStatus({ user, isLoading, error }) {
if (isLoading) {
return <div>Loading...</div>;
} else {
if (error) {
return <div>Error: {error.message}</div>;
} else {
if (user.isActive) {
if (user.isPremium) {
return <div>Premium User</div>;
}
}
}
}
}
// ✅ Хорошо - ранний выход
function UserStatus({ user, isLoading, error }) {
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
if (!user) return null;
return user.isPremium ? <PremiumBadge /> : <StandardBadge />;
}
7. Консистентность структуры
Элементы компонента должны быть организованы логично:
// ✅ Хорошо - логичный порядок
function UserCard({ userId }: UserCardProps) {
// 1. Хуки для состояния
const [isExpanded, setIsExpanded] = useState(false);
// 2. Хуки для эффектов
const { user, isLoading } = useUser(userId);
// 3. Вычисляемые значения
const displayName = user?.firstName && user?.lastName
? `${user.firstName} ${user.lastName}`
: user?.email;
// 4. Обработчики событий
const handleExpand = () => setIsExpanded(!isExpanded);
// 5. Условный рендер
if (isLoading) return <Skeleton />;
if (!user) return null;
// 6. Основной рендер
return (
<div className="card">
<header>{displayName}</header>
{isExpanded && <details>{/* ... */}</details>}
<footer>
<button onClick={handleExpand}>Toggle</button>
</footer>
</div>
);
}
8. Использование слотов/composition
// ✅ Хорошо - композиция компонентов
function Dialog({ isOpen, onClose, title, children }: DialogProps) {
if (!isOpen) return null;
return (
<div className="modal-overlay" onClick={onClose}>
<div className="modal" onClick={e => e.stopPropagation()}>
<header>{title}</header>
<main>{children}</main>
<footer>
<button onClick={onClose}>Close</button>
</footer>
</div>
</div>
);
}
// Использование
<Dialog isOpen={isOpen} onClose={() => setIsOpen(false)} title="Confirm">
<p>Are you sure?</p>
<button onClick={handleConfirm}>Yes</button>
</Dialog>
9. Отсутствие side effects
Компонент хорошо читается, если нет неожиданных побочных эффектов:
// ❌ Плохо - неожиданные побочные эффекты
function UserList({ users }) {
users.forEach(user => {
// Побочный эффект в теле компонента!
user.lastViewed = new Date();
});
return <ul>{users.map(u => <li>{u.name}</li>)}</ul>;
}
// ✅ Хорошо - эффекты контролируются
function UserList({ users }: UserListProps) {
useEffect(() => {
// Побочный эффект явно в useEffect
const updateLastViewed = async () => {
await api.updateLastViewed(users.map(u => u.id));
};
updateLastViewed();
}, [users]);
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
10. Комментарии только где необходимо
// ❌ Плохо - лишние комментарии
function calculateTotal(items) {
// Инициализируем переменную total нулём
let total = 0;
// Итерируем по каждому элементу в массиве items
for (const item of items) {
// Добавляем цену товара к total
total += item.price;
}
// Возвращаем итоговую сумму
return total;
}
// ✅ Хорошо - самодокументирующийся код
function calculateTotal(items: CartItem[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
// Комментарий только для non-obvious логики
function debounce<T extends (...args: any[]) => void>(fn: T, delay: number) {
let timeoutId: ReturnType<typeof setTimeout>;
// Используем стрелочную функцию чтобы сохранить контекст 'this'
return ((...args: Parameters<T>) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn(...args), delay);
}) as T;
}
Чеклист читаемости компонента
- Компонент занимает менее 100 строк
- Пропсы типизированы и имеют описательные имена
- Логика отделена от представления (хуки)
- Нет вложенных условных операторов (максимум 1 уровень)
- Каждый компонент отвечает за одно
- Имена переменных понятны без комментариев
- Используется early return для условной логики
- Side effects находятся только в useEffect
- Компонент легко тестировать
- Нет хардкодированных значений и магических чисел
Хорошо читаемый код - это инвестиция в будущее проекта.