티스토리 뷰
이전글: https://orashelter.tistory.com/93
3. 리스코프 치환 원칙 (LSP, Liskov Substitution Principle)
서브 타입은 언제나 자신의 기반(부모) 타입으로 교체할 수 있어야 함을 의미합니다.
이를 리액트로 설명하자면 컴포넌트를 상속할때 부모 컴포넌트의 Props를 그대로 사용할 수 있어야 함을 의미합니다.
특정 컴포넌트에서 파생된 다른 컴포넌트가 부모 컴포넌트의 기능을 변경하게 되면 해당 컴포넌트를 사용할 때마다 추가적인 문서화가 필요해져서 프로젝트의 유지보수가 복잡해질 수 있습니다.
반면 LSP를 준수하면 동작을 예측할 수 있어 유지보수가 더 용이해집니다.
버튼 컴포넌트를 예시로 LSP를 준수하는 IconButton과 LoadingButton을 만들어보겠습니다.
// 부모 컴포넌트
function Button({ onClick, children }) {
return (
<button onClick={onClick}>
{children}
</button>
)
}
// 자식 컴포넌트 - 1
function IconButton({ icon, onClick, children}) {
return (
<button onClick={onClick}>
{icon}
<span>{children}</span>
</button>
)
}
// 자식 컴포넌트 - 2
function LoadingButton({ isLoading, onClick, children ) {
return (
<button onClick={onClick}>
{isLoading ? <span className="spinner" /> : children}
</button>
)
}
IconButton과 LoadingButton 컴포넌트는 부모 컴포넌트인 Button을 완벽히 대체할 수 있어 LSP를 준수합니다.
4. 인터페이스 분리 원칙 (ISP, Interface Segregation Principle)
하나의 클래스는 자신이 이용하지 않는 인터페이스에 의존하지 않아야 함을 의미합니다.
이를 리액트로 설명하자면 리액트에서는 컴포넌트에 사용하지 않는 props에 의존하지 말아야 함을 의미합니다.
필요한 데이터만 전달 받아 해당 데이터에만 컴포넌트가 의존하게 만든다면 다른 데이터, 다른 컴포넌트에서도 재사용이 가능한 컴포넌트가 될 수 있습니다.
인터페이스 분리 원칙을 준수한 예제를 작성해보겠습니다.
function UserProfile({ user }) {
return (
<div className="user-profile">
<Avatar src={user.avatarUrl} />
<UserInfo name={user.name} email={user.email} />
<UserActions userId={user.id} />
</div>
);
};
function Avatar({ src }) {
return <img src={src} alt="avatar" />;
};
function UserInfo({ name, email }) {
return (
<div>
<h2>{name}</h2>
<p>{email}</p>
</div>
);
};
function UserActions({ userId }) {
const handleEdit = () => {
// 유저 수정 로직
};
const handleDelete = () => {
// 유저 삭제 로직
};
return (
<div>
<button onClick={handleEdit}>수정</button>
<button onClick={handleDelete}>삭제</button>
</div>
);
};
위의 예제의 각 컴포넌트는 필수적인 데이터만 전달 받아 의존하는 컴포넌트를 작성하여, 컴포넌트 간의 의존성을 낮추고 재사용 성을 높일 수 있습니다.
5. 의존성 역전 원칙 (DIP, Dependency Inversion Principle)
개인적으로 가장 어려운 부분입니다.
고수준 모듈은 저수준 모듈로부터 아무것도 가져오지 않아야 하며, 둘 다 추상화에 의존해야 함을 의미합니다.
이를 리액트로 설명하자면 보통 리액트 훅과 HOC로 설명할 수 있습니다.
HOC를 이용해 DIP를 구현해보겠습니다.
export function withSuspense(Component, suspenseProps) {
const Wrapped = forwardRef((props, ref) => {
const FallbackComponent = suspenseProps.FallbackComponent;
return (
<Suspense
fallback={suspenseProps.fallback || (FallbackComponent && <FallbackComponent />)}
>
<Component {...props} ref={ref} />
</Suspense>
);
});
const name = Component.displayName || Component.name || 'Unknown';
Wrapped.displayName = `withSuspense(${name})`;
return Wrapped;
}
function UserList() {
const { data: users, error } = useSuspenseQuery({ queryKey: ['users'], queryFn: fetchUsers, throwOnError: false })
if (error) return <div>에러 발생: {error.message}</div>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
const UserListWithSuspense = withSuspense(UserList, { FallbackComponent: Loader })
위 코드는 고수준 모듈(HOC를 사용하는 코드)은 저수준 모듈(HOC 내부 구현)에 의존하지 않으며, 둘 다 추상화(HOC의 인터페이스)에 의존합니다.
추상화(HOC의 인터페이스)는 세부 사항에 의존하지 않으며, 세부 사항(HOC의 내부 구현)이 추상화에 의존합니다.
'javascript > react' 카테고리의 다른 글
개발 원칙 With SOLID - 1 (0) | 2024.10.14 |
---|---|
useState vs useReducer 어떤걸 써야할까? (0) | 2024.06.24 |
리액트 함수 컴포넌트의 생명 주기란? (0) | 2024.06.24 |
React에서 State와 Props는 어떤 차이가 있을까? (0) | 2024.06.24 |
- Total
- Today
- Yesterday
- Windows
- 팩토리 메소드 패턴
- django
- 심플 팩토리 패턴
- 팩토리 패턴
- 깨짐
- Apache
- Git
- 한글깨짐
- 안드로이드
- git log
- 구글 맵
- 파이썬
- cmd
- 플라스크
- Python
- Windwos
- 추상 클래스
- 웹
- ㄹ
- 한글 깨짐
- 구글맵
- 한글
- Google Map
- flask
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |