본문 바로가기

Programming/React.js

React 개발자로서 알아야 할 SOLID 원칙

반응형

프론트엔드 개발을 하다 보면 컴포넌트가 점점 커지고, 재사용도 어려워지고, 유지보수는 지옥이 되어가는 경험... 한 번쯤 해보셨죠?
이럴 때 도움이 되는 게 바로 SOLID 원칙입니다.

SOLID는 객체 지향 설계 원칙이지만, 함수형과 컴포넌트 기반 개발에도 충분히 적용할 수 있어요.
오늘은 React 코드 예시를 통해 SOLID를 쉽게 풀어보겠습니다!


✅ 1. SRP - 단일 책임 원칙 (Single Responsibility Principle)

한 컴포넌트는 하나의 일만 해야 한다.

❌ 잘못된 예시

const UserProfile = ({ user }) => {
  const [editing, setEditing] = useState(false);

  const saveUser = async (userData) => {
    // 서버 요청
  };

  return (
    <div>
      <img src={user.avatar} />
      {editing ? (
        <input value={user.name} />
      ) : (
        <p>{user.name}</p>
      )}
      <button onClick={() => setEditing(!editing)}>Edit</button>
      <button onClick={() => saveUser(user)}>Save</button>
    </div>
  );
};

✅ 개선된 예시

const UserAvatar = ({ avatar }) => <img src={avatar} alt="avatar" />;
const UserName = ({ name, editing, onChange }) =>
  editing ? <input value={name} onChange={onChange} /> : <p>{name}</p>;

const UserActions = ({ onEdit, onSave }) => (
  <>
    <button onClick={onEdit}>Edit</button>
    <button onClick={onSave}>Save</button>
  </>
);

const UserProfile = ({ user }) => {
  const [editing, setEditing] = useState(false);
  const handleSave = () => {
    // 서버 요청
  };

  return (
    <div>
      <UserAvatar avatar={user.avatar} />
      <UserName name={user.name} editing={editing} onChange={() => {}} />
      <UserActions
        onEdit={() => setEditing(!editing)}
        onSave={handleSave}
      />
    </div>
  );
};

✅ 2. OCP - 개방/폐쇄 원칙 (Open/Closed Principle)

확장에는 열려 있고, 수정에는 닫혀 있어야 한다.

예시: 버튼 스타일 확장 가능한 설계

const Button = ({ variant = 'default', children, ...props }) => {
  const className = {
    default: 'bg-gray-200 text-black',
    primary: 'bg-blue-500 text-white',
    danger: 'bg-red-500 text-white',
  }[variant];

  return (
    <button className={className} {...props}>
      {children}
    </button>
  );
};

새로운 스타일이 필요할 때 기존 컴포넌트를 수정하지 않고 variant만 확장하면 됩니다.


✅ 3. LSP - 리스코프 치환 원칙 (Liskov Substitution Principle)

상위 타입을 사용하는 곳에 하위 타입을 넣어도 잘 동작해야 한다.

예시

const Notification = ({ message }) => <div>{message}</div>;

const SuccessNotification = (props) => (
  <Notification {...props} message={`✅ ${props.message}`} />
);

const ErrorNotification = (props) => (
  <Notification {...props} message={`❌ ${props.message}`} />
);

SuccessNotification, ErrorNotification 모두 Notification 자리에 들어가도 문제 없이 작동합니다.


✅ 4. ISP - 인터페이스 분리 원칙 (Interface Segregation Principle)

필요한 기능만 가지는 작은 props 설계를 하자.

❌ 너무 많은 props를 가진 컴포넌트

const Form = ({ onSubmit, onReset, onCancel, onValidate, onSave }) => {
  // ...
};

✅ 기능별로 나누기

const SubmitButton = ({ onSubmit }) => <button onClick={onSubmit}>Submit</button>;
const CancelButton = ({ onCancel }) => <button onClick={onCancel}>Cancel</button>;

필요한 기능만 사용하는 컴포넌트를 만들면 더 유연하고 재사용성도 높아져요.


✅ 5. DIP - 의존성 역전 원칙 (Dependency Inversion Principle)

상위 모듈이 하위 모듈에 직접 의존하지 말고, 추상화에 의존하자.

예시: API 요청을 추상화

const useUserService = (apiClient) => {
  const getUser = async (id) => await apiClient.get(`/users/${id}`);
  return { getUser };
};

const apiClient = {
  get: (url) => fetch(url).then(res => res.json())
};

const UserContainer = () => {
  const { getUser } = useUserService(apiClient);

  useEffect(() => {
    getUser(1).then(console.log);
  }, []);

  return <div>Loading user...</div>;
};

apiClient를 추상화하면 테스트나 교체가 매우 쉬워집니다!


✨ 마무리

React 컴포넌트 기반 개발에서도 SOLID 원칙은 강력한 가이드가 되어줍니다.

📌 정리하자면:

  • SRP: 하나의 책임만!
  • OCP: 확장 가능하게!
  • LSP: 대체 가능하게!
  • ISP: 필요한 것만!
  • DIP: 추상화에 의존!
반응형