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

Как понять что компонент самодостаточен?

2.2 Middle🔥 201 комментариев
#React

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

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

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

Самодостаточность React компонента

Самодостаточный (self-contained) компонент - это компонент, который может работать независимо и не зависит от внешних состояний. Это ключевой принцип компонентной архитектуры, обеспечивающий переиспользуемость и масштабируемость.

Критерии самодостаточности

Компонент считается самодостаточным, если он:

// НЕПРАВИЛЬНО - зависит от глобального состояния
function UserCard() {
  // Зависит от глобального Redux хранилища
  const user = useSelector(state => state.user);
  const updateUser = useDispatch(updateUserAction);
  
  if (!user) return <div>No user data</div>;
  
  return (
    <div>
      <h1>{user.name}</h1>
      <button onClick={() => updateUser()}>Update</button>
    </div>
  );
}

// Использование: работает только если в Redux есть user
<UserCard /> // Зависит от глобального состояния!

// ПРАВИЛЬНО - самодостаточный компонент
interface UserCardProps {
  user: User;
  onUpdate?: (user: User) => Promise<void>;
  isLoading?: boolean;
}

function UserCard({ user, onUpdate, isLoading = false }: UserCardProps) {
  const [isUpdating, setIsUpdating] = useState(false);
  
  const handleUpdate = async () => {
    if (!onUpdate) return;
    
    try {
      setIsUpdating(true);
      await onUpdate(user);
    } finally {
      setIsUpdating(false);
    }
  };
  
  return (
    <div>
      <h1>{user.name}</h1>
      <button 
        onClick={handleUpdate}
        disabled={isLoading || isUpdating}
      >
        {isUpdating ? 'Updating...' : 'Update'}
      </button>
    </div>
  );
}

// Использование: передаём всё необходимое через props
<UserCard 
  user={user} 
  onUpdate={handleUpdate}
  isLoading={loading}
/>

Props как контракт компонента

// НЕПРАВИЛЬНО - нет контракта (any props)
function Button(props: any) {
  return <button {...props}>{props.children}</button>;
}

// ПРАВИЛЬНО - явный контракт через interface
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  isLoading?: boolean;
  children: React.ReactNode;
}

function Button({
  variant = 'primary',
  size = 'md',
  isLoading = false,
  children,
  ...rest
}: ButtonProps) {
  return (
    <button 
      className={`btn btn-${variant} btn-${size}`}
      disabled={isLoading}
      {...rest}
    >
      {isLoading ? 'Loading...' : children}
    </button>
  );
}

// Использование: ясно какие props доступны
<Button variant="primary" size="lg" onClick={handleClick}>
  Click me
</Button>

Локальное состояние vs внешние зависимости

// НЕПРАВИЛЬНО - много внешних зависимостей
function SearchUsers() {
  // API вызывается автоматически
  const { data: results } = useFetchUsers();
  
  // Поиск управляется где-то в Redux
  const query = useSelector(state => state.search.query);
  const dispatch = useDispatch();
  
  // Нет контроля над behavior компонента
  return (
    <div>
      {results.map(user => <div key={user.id}>{user.name}</div>)}
    </div>
  );
}

// ПРАВИЛЬНО - компонент контролирует свой state
interface SearchUsersProps {
  onSearch?: (query: string) => Promise<User[]>;
  debounceMs?: number;
}

function SearchUsers({ onSearch, debounceMs = 300 }: SearchUsersProps) {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState<User[]>([]);
  const [isLoading, setIsLoading] = useState(false);
  
  // Компонент контролирует поиск через useEffect
  useEffect(() => {
    if (!onSearch || !query) {
      setResults([]);
      return;
    }
    
    const timer = setTimeout(async () => {
      setIsLoading(true);
      try {
        const data = await onSearch(query);
        setResults(data);
      } finally {
        setIsLoading(false);
      }
    }, debounceMs);
    
    return () => clearTimeout(timer);
  }, [query, onSearch, debounceMs]);
  
  return (
    <div>
      <input
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="Search users..."
      />
      {isLoading && <Spinner />}
      {results.map(user => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  );
}

// Использование: полный контроль над компонентом
<SearchUsers onSearch={api.searchUsers} debounceMs={500} />

Составление из самодостаточных компонентов

// ПРАВИЛЬНО - маленькие самодостаточные компоненты
interface CardProps {
  title: string;
  children: React.ReactNode;
  onClose?: () => void;
}

function Card({ title, children, onClose }: CardProps) {
  return (
    <div className="card">
      <div className="card-header">
        <h2>{title}</h2>
        {onClose && <button onClick={onClose}>X</button>}
      </div>
      <div className="card-body">{children}</div>
    </div>
  );
}

interface FormProps {
  initialData?: Record<string, string>;
  onSubmit: (data: Record<string, string>) => Promise<void>;
  fields: Array<{ name: string; label: string; type: string }>;
}

function Form({ initialData = {}, onSubmit, fields }: FormProps) {
  const [data, setData] = useState(initialData);
  const [isLoading, setIsLoading] = useState(false);
  
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setIsLoading(true);
    try {
      await onSubmit(data);
    } finally {
      setIsLoading(false);
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      {fields.map(field => (
        <input
          key={field.name}
          type={field.type}
          value={data[field.name] || ''}
          onChange={e => setData({ ...data, [field.name]: e.target.value })}
          placeholder={field.label}
        />
      ))}
      <button type="submit" disabled={isLoading}>
        {isLoading ? 'Saving...' : 'Save'}
      </button>
    </form>
  );
}

// Использование: компонуем самодостаточные компоненты
function UserFormDialog({ onClose, onSave }: {
  onClose: () => void;
  onSave: (data: UserData) => Promise<void>;
}) {
  return (
    <Card title="Create User" onClose={onClose}>
      <Form
        onSubmit={onSave}
        fields={[
          { name: 'name', label: 'Name', type: 'text' },
          { name: 'email', label: 'Email', type: 'email' },
        ]}
      />
    </Card>
  );
}

Контрольный список самодостаточности

  • Компонент работает с переданными props
  • Нет обращения к глобальному состоянию (Redux, Context)
  • Все необходимые данные передаются через props
  • Все обработчики событий передаются через props
  • Локальное состояние используется только для UI state
  • Компонент не зависит от импортов из других частей приложения
  • Есть интерфейс props с типизацией
  • Компонент работает в изоляции (Storybook)
  • Можно переиспользовать в разных контекстах
  • Тесты не требуют настройки Redux/Context

Самодостаточный компонент - это компонент, который легко тестировать, переиспользовать и поддерживать. Это должна быть цель при разработке любого компонента React.

Как понять что компонент самодостаточен? | PrepBro