Расскажи про любимый принцип из SOLID
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Мой любимый принцип SOLID: Single Responsibility Principle
Из всех пяти принципов SOLID я выбираю Single Responsibility Principle (SRP) — принцип единственной ответственности. Для Frontend-разработчика этот принцип критически важен и даёт наибольший прирост в качестве кода.
Что это такое?
Single Responsibility Principle гласит: каждый модуль, класс или компонент должен иметь ровно одну причину для изменения. Иначе говоря, компонент должен отвечать ровно за одно — либо за отображение (View), либо за логику (Logic), либо за работу с данными (Data).
Почему это важно для фронтенда?
На фронтенде разработчики часто создают монолитные компоненты, которые одновременно:
- Загружают данные с сервера
- Обрабатывают состояние
- Отображают UI
- Обрабатывают события пользователя
Такие компоненты трудно тестировать, переиспользовать и менять.
// ❌ Плохо: компонент отвечает за всё
function UserProfile() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// Загрузка данных
fetch("/api/user")
.then(r => r.json())
.then(data => setUser(data))
.catch(e => setError(e));
}, []);
// Обновление пользователя
const handleUpdate = async (newData) => {
const response = await fetch("/api/user", {
method: "PUT",
body: JSON.stringify(newData)
});
// ... обновление состояния
};
// Логика валидации
const isEmailValid = user?.email?.includes("@");
// Всё в одном компоненте!
return (
<div>
{loading && <Spinner />}
{error && <ErrorMessage error={error} />}
{user && (
<form>
<input value={user.name} />
<input value={user.email} />
<button onClick={() => handleUpdate({...user})}>
Save
</button>
</form>
)}
</div>
);
}
Правильное применение SRP
Разделим ответственность на несколько слоёв:
// 1. Хук для работы с данными (Data Layer)
function useUser(userId) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetchUser(userId)
.then(setUser)
.catch(setError)
.finally(() => setLoading(false));
}, [userId]);
const updateUser = async (newData) => {
const updated = await updateUserAPI(userId, newData);
setUser(updated);
};
return { user, loading, error, updateUser };
}
// 2. Утилита для валидации (Business Logic)
const userValidation = {
isEmailValid: (email) => email?.includes("@"),
isNameValid: (name) => name?.length > 2,
};
// 3. Компонент только для отображения (Presentation)
function UserProfileForm({ user, onUpdate, isLoading }) {
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData(e.target);
onUpdate(Object.fromEntries(formData));
};
if (isLoading) return <Spinner />;
return (
<form onSubmit={handleSubmit}>
<input name="name" defaultValue={user.name} />
<input name="email" defaultValue={user.email} />
<button type="submit">Save</button>
</form>
);
}
// 4. Контейнер, который собирает всё вместе
function UserProfileContainer({ userId }) {
const { user, loading, error, updateUser } = useUser(userId);
if (error) return <ErrorMessage error={error} />;
if (!user) return null;
return (
<UserProfileForm
user={user}
onUpdate={updateUser}
isLoading={loading}
/>
);
}
Преимущества SRP
- Простота тестирования: каждый компонент отвечает за одно, его легче мокировать
// ✅ Просто тестировать UserProfileForm
describe("UserProfileForm", () => {
it("submits form data", () => {
const mockUser = { name: "John", email: "john@example.com" };
const mockOnUpdate = jest.fn();
render(
<UserProfileForm user={mockUser} onUpdate={mockOnUpdate} />
);
fireEvent.change(screen.getByDisplayValue("John"), {
target: { value: "Jane" }
});
fireEvent.click(screen.getByText("Save"));
expect(mockOnUpdate).toHaveBeenCalled();
});
});
- Переиспользование: компоненты можно использовать в разных контекстах
// Одна форма для редактирования профиля
<UserProfileForm user={user} onUpdate={updateUser} />
// Та же форма для создания нового пользователя
<UserProfileForm user={emptyUser} onUpdate={createUser} />
- Поддерживаемость: изменения в одной части не влияют на другие
// Можем изменить API без тронутия компонента UI
async function fetchUser(userId) {
// Была fetch, теперь GraphQL
const query = gql`query { user(id: $userId) { name email } }`;
return apolloClient.query({ query, variables: { userId } });
}
// UserProfileForm не требует изменений!
- Переиспользование логики: хук
useUserработает везде
// В разных компонентах
function UserHeader() {
const { user } = useUser(userId);
return <h1>{user.name}</h1>;
}
function UserStats() {
const { user } = useUser(userId);
return <div>Email: {user.email}</div>;
}
Практический пример: список комментариев
// ✅ Правильно: разделение ответственности
// 1. Логика загрузки
function useComments(postId) {
const [comments, setComments] = useState([]);
useEffect(() => {
loadComments(postId).then(setComments);
}, [postId]);
return comments;
}
// 2. Отображение одного комментария
function CommentItem({ comment }) {
return (
<div className="comment">
<strong>{comment.author}</strong>
<p>{comment.text}</p>
</div>
);
}
// 3. Список комментариев
function CommentsList({ comments, isLoading }) {
if (isLoading) return <Spinner />;
return <div>{comments.map(c => <CommentItem key={c.id} comment={c} />)}</div>;
}
// 4. Контейнер
function PostComments({ postId }) {
const comments = useComments(postId);
return <CommentsList comments={comments} />;
}
Заключение
Single Responsibility Principle — основа чистого фронтенд-кода. Компоненты, которые делают одно и делают это хорошо, легче понимать, тестировать и переиспользовать. Это напрямую снижает количество багов и упрощает добавление новых функций.