최근 익히게 된 리액트 코드 리팩토링의 개념 중 하나인 Component Composition 에 대하여 정리해 본다.
일반적으로 components composition(컴포넌트 합성) 이란 컴포넌트를 작은 단위로 분리하고 재조합 하여 재사용성을 높이는 것을 말한다. 예를 들어, 한 앱 내에서 여러번 사용되는 비슷한 모양의 버튼을 하나의 재사용 가능한 컴포넌트로 만들어 필요한 곳에서 불러 쓸 수 있다:
import styled from "styled-components";
import { theme } from "../styles/theme";
export default function Button<P>(props: PropsWithChildren<P>) {
return <Container {...props} />;
}
const Container = styled.button`
background-color: ${theme.buttonBackgroundColor};
color: white;
width: 10rem;
height: 2rem;
border: none;
border-radius: 6px;
cursor: pointer;
`;
위의 경우, 단순히 하나의 styled component로 다시 보내주는 형태이기 때문에 props 전체를 그대로 전달하였지만, 만약 두개 이상의 태그 등으로 중첩되어 있다면 children 이 들어가야 할 자리에 {props.children} 을 명시해주면 된다.
이번에 이 패턴을 가지고 여러가지로 시험을 해보면서 깨달은 것은 컴포넌트 합성은 단순히 특정 UI 모습만 담은 작은 단위의 컴포넌트에만 쓸 수 있는 것이 아니라는 점이다. 부모가 관리하는 상태값과 연관된 비지니스 로직의 기능도 주고 싶을 경우, props로 상태값이나 setState를 넘겨줄 수 있지만, 반대로 자식에게 응답을 얻고 싶을때는 콜백 함수를 넘겨줌으로써 사용할 수 있다.
다음은 내가 만들었던 confirmation 모달창에 위의 버튼 컴포넌트를 적용한 사례이다. 사용자의 컨펌을 받아야 하는 모달이 여러 곳에서 존재할 경우, 안의 알림 내용은 children 으로 받고, 버튼이 눌렸을 때 확인/취소에 따라 콜백으로 받아온 함수에 true/false를 전달해준다.
import styled from "styled-components";
import Button from "../components/Button";
import { theme } from "../styles/theme";
type ConfirmationModalType = {
callback: (isConfirmed: boolean) => void;
};
export default function ConfirmationModal({ callback, children }: ConfirmationModalType) {
return (
<Container>
<h3>{children}</h3>
<ButtonWrapper>
<YesButton onClick={() => callback(true)}>Yes</YesButton>
<NoButton onClick={() => callback(false)}>No</NoButton>
</ButtonWrapper>
</Container>
);
}
const Container = styled.div`
...
`;
const ButtonWrapper = styled.div`
...
`;
const YesButton = styled(Button)`
...
`;
const NoButton = styled(Button)`
...
`;
부모 컴포넌트에서는 다음과 같은 방법으로 콜백을 전달한다:
...
{deleteRequestedID && (
<ConfirmationModal
callback={(isConfirmed) => {
if (isConfirmed && deleteRequestedID) deleteSchedule(deleteRequestedID);
setDeleteRequestedID(null);
}}
>
Are you sure to delete this schedule?
</ConfirmationModal>
)}
...
이런식으로 단순한 UI 로직만 분리하는 용도 뿐 아니라 로직을 수반한 더 큰 단위의 컴포넌트도 분리하여 재사용/재조합 할 수 있다.
(참고: 리액트 공식문서 - https://reactjs.org/docs/composition-vs-inheritance.html)