Комментарии (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.