Где нужно использовать классы в React?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Использование классов в React
В современном React предпочтение отдаётся функциональным компонентам с хуками. Однако класс-компоненты все ещё имеют место в определённых сценариях.
Почему функциональные компоненты - приоритет
В современной разработке функциональные компоненты с хуками - это стандарт. Они:
- Проще писать и понимать
- Имеют меньше boilerplate кода
- Легче тестировать
- Лучше с точки зрения производительности
- Поддерживаются новыми возможностями React (Suspense, Concurrent features)
// Современный подход - функциональный компонент
function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUser(userId).then(setUser).finally(() => setLoading(false));
}, [userId]);
if (loading) return <p>Загрузка...</p>;
return <div>{user?.name}</div>;
}
Сценарии использования класс-компонентов
1. Error Boundaries (обработка ошибок)
Error Boundaries можно создавать только на основе классов. Это единственный официальный способ обработки ошибок в React:
class ErrorBoundary extends React.Component<
{ children: React.ReactNode },
{ hasError: boolean; error: Error | null }
> {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error) {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('Error caught:', error, errorInfo);
// Логируем ошибку на сервис мониторинга (Sentry, etc.)
}
render() {
if (this.state.hasError) {
return (
<div>
<h1>Что-то пошло не так</h1>
<p>{this.state.error?.message}</p>
</div>
);
}
return this.props.children;
}
}
// Использование
<ErrorBoundary>
<App />
</ErrorBoundary>
Для функциональных компонентов можно использовать библиотеки вроде react-error-boundary:
import { ErrorBoundary } from 'react-error-boundary'
function ErrorFallback({ error, resetErrorBoundary }) {
return (
<div>
<h1>Что-то пошло не так</h1>
<p>{error.message}</p>
<button onClick={resetErrorBoundary}>Попробовать снова</button>
</div>
)
}
<ErrorBoundary FallbackComponent={ErrorFallback}>
<App />
</ErrorBoundary>
2. Legacy код и миграция
Если работаете с существующим проектом, где используются класс-компоненты, нет смысла немедленно всё переписывать:
// Старый код, который работает - оставьте как есть
class LegacyComponent extends React.Component {
state = { count: 0 };
render() {
return (
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Count: {this.state.count}
</button>
);
}
}
// Новые компоненты пишите функциональными
function ModernComponent() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}
3. Нишевые случаи с жизненными циклами
Для очень специфичных сценариев, где нужна полная власть над жизненным циклом:
class DataFetcher extends React.Component<{
url: string
onData: (data: any) => void
}> {
componentDidMount() {
this.fetchData();
}
componentDidUpdate(prevProps) {
if (prevProps.url !== this.props.url) {
this.fetchData();
}
}
componentWillUnmount() {
this.abortController?.abort();
}
private abortController = new AbortController();
async fetchData() {
try {
const response = await fetch(this.props.url, {
signal: this.abortController.signal
});
const data = await response.json();
this.props.onData(data);
} catch (error) {
console.error(error);
}
}
render() {
return <p>Загрузка...</p>;
}
}
Но то же самое легче с хуками:
function DataFetcher({ url, onData }: { url: string; onData: (data: any) => void }) {
useEffect(() => {
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.then(res => res.json())
.then(onData)
.catch(console.error);
return () => controller.abort();
}, [url, onData]);
return <p>Загрузка...</p>;
}
Сравнение: Класс vs Функциональный компонент
// Класс-компонент
class TodoList extends React.Component<{ items: string[] }> {
state = { newItem: ' };
handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ newItem: e.target.value });
};
handleAddTodo = () => {
console.log('Added:', this.state.newItem);
this.setState({ newItem: ' });
};
render() {
return (
<div>
<ul>
{this.props.items.map((item, i) => (
<li key={i}>{item}</li>
))}
</ul>
<input value={this.state.newItem} onChange={this.handleChange} />
<button onClick={this.handleAddTodo}>Добавить</button>
</div>
);
}
}
// Функциональный компонент (современный подход)
function TodoList({ items }: { items: string[] }) {
const [newItem, setNewItem] = useState(')
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setNewItem(e.target.value);
};
const handleAddTodo = () => {
console.log('Added:', newItem);
setNewItem(');
};
return (
<div>
<ul>
{items.map((item, i) => (
<li key={i}>{item}</li>
))}
</ul>
<input value={newItem} onChange={handleChange} />
<button onClick={handleAddTodo}>Добавить</button>
</div>
);
}
Когда НЕ использовать классы
// НЕПРАВИЛЬНО - класс для простого компонента
class Welcome extends React.Component {
render() {
return <h1>Привет, {this.props.name}!</h1>;
}
}
// ПРАВИЛЬНО - функциональный компонент
function Welcome({ name }: { name: string }) {
return <h1>Привет, {name}!</h1>;
}
// НЕПРАВИЛЬНО - класс для управления состоянием
class Counter extends React.Component {
state = { count: 0 };
render() {
return (
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Count: {this.state.count}
</button>
);
}
}
// ПРАВИЛЬНО - функция с хуком
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}
Общая стратегия
Правило 95:5:
- 95% компонентов должны быть функциональными с хуками
- 5% случаев - это Error Boundaries и legacy код
// Структура современного React приложения
export function App() {
return (
<ErrorBoundary> {/* Класс-компонент для обработки ошибок */}
<Layout>
<HomePage /> {/* Функциональный компонент */}
<UserProfile /> {/* Функциональный компонент */}
<Settings /> {/* Функциональный компонент */}
</Layout>
</ErrorBoundary>
);
}
Итог
- Используйте функциональные компоненты с хуками - это современный стандарт
- Используйте классы только для Error Boundaries - это единственный встроенный способ
- Для legacy кода - миграция постепенная, не спешите
- Не смешивайте стили - либо классы, либо функции, но не вместе
- При необходимости обработки ошибок - используйте
react-error-boundaryдля функциональных компонентов
Современный React - это фактически React функциональных компонентов и хуков.