← Назад к вопросам

Как по коду компонента определяешь что он хорошо читается?

2.0 Middle🔥 121 комментариев
#React

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Читаемость кода компонента

Определение качества читаемости компонента требует анализа нескольких факторов. Хороший компонент должен быть понятным с первого взгляда, легко поддерживаемым и тестируемым.

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
  • Компонент легко тестировать
  • Нет хардкодированных значений и магических чисел

Хорошо читаемый код - это инвестиция в будущее проекта.