한국어로는 개방-폐쇄 원칙
이라고 부른다.
객체는 확장에는 열려 있고 변경에는 닫혀 있어야 한다는 원칙이다.
다시 말해 기능이 추가되거나 변경될 때 기존 코드를 변경하지 않아도 되어야 한다는 의미이다.
버튼 컴포넌트를 예시로 들겠다.
// Button.tsx
import {
HiOutlineArrowNarrowRight,
HiOutlineArrowNarrowLeft,
} from "react-icons/hi";
interface IButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
text: string;
role?: "back" | "forward";
}
export function Button(props: IButtonProps) {
const { text, role } = props;
return (
<button {...props}>
{text}
<div>
{role === "forward" && <HiOutlineArrowNarrowRight />
{role === "back" && <HiOutlineArrowNarrowLeft />}
</div>
</button>
);
}
// index.tsx
import { Button } from "./button";
export function ButtonsPage() {
return (
<div>
<Button
text="Go Home"
role="forward"
/>
<Button
text="Go Back"
role="back"
/>
</div>
);
}
Button
컴포넌트는 props
를 전달받고, 그 안의 role
이라는 property를 기반으로 버튼에 들어가는 아이콘을 결정하고 있다.
만약 새로운 role
을 추가해야 하는 상황이 발생한다면
1. IButtonProps
에서 role
에 새로운 값을 추가하고
2. react-icons/hi
로부터 새로운 아이콘을 import하고
3. Button
내부에 조건문과 import한 아이콘을 추가해야 한다.
위 컴포넌트는 OCP를 준수하지 않고 있다.
확장을 위해선 기존 코드의 많은 부분을 변경해야 하기 때문이다.
여러 개의 role
이 추가된다면 코드가 쓸데없이 길어지고 복잡해질 것이다.
OCP를 준수한 코드로 개선해보겠다.
// Button.tsx
interface IButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
text: string;
icon?: React.ReactNode;
}
export function Button(props: IButtonProps) {
const { text, icon } = props;
return (
<button {...props}>
{text}
<div>
{icon}
</div>
</button>
);
}
// index.tsx
import { Button } from "./button";
import {
HiOutlineArrowNarrowRight,
HiOutlineArrowNarrowLeft,
} from "react-icons/hi";
export function ButtonsPage() {
return (
<div>
<Button
text="Go Home"
icon={<HiOutlineArrowNarrowRight />}
/>
<Button
text="Go Back"
icon={<HiOutlineArrowNarrowLeft />}
/>
</div>
);
}
React 컴포넌트는 props로 전달이 가능하기 때문에, 이런 식으로 role
을 없애고 Button
을 사용하는 곳에서 icon
컴포넌트를 직접 import해서 props
로 전달해주도록 하면 자유롭게 확장이 가능하고 기존의 Button
코드를 변경할 필요도 없다.
- OCP는 객체가 변경에는 닫혀 있고 확장에는 열려 있어야 한다는 원칙이다.
- 컴포넌트의 내부 코드를 직접 수정하지 않아도 기능의 확장이 가능해야 한다.
- 조건에 따라 다른 자식 컴포넌트를 렌더링하는 컴포넌트에는 자식 컴포넌트를
props
로 전달한다.