2026-09-10: 설명과 코드 예시를 보완했습니다. 아래 예시는 글의 설계 의도를 전달하기 위한 것이며, 원 프로젝트에 반영된 변경 내역과는 구분합니다.
버튼에 공통 스타일을 넣어두었는데, 어떤 곳에서는 그 모양 그대로 링크를 쓰고 싶을 때가 있다. 스타일을 다시 적자니 중복이고, 링크 바깥에 button을 하나 더 감싸면 인터랙티브 요소가 중첩된다. 자식이 실제 DOM을 맡고 공통 스타일과 동작을 전달받게 하면 어떨까. asChild와 Slot 패턴을 살펴본 이유다.
무엇을 전달하고 무엇을 먼저 실행할까
JSX 요소는 비교하고 복제할 수 있지만, 참조가 같다는 조건에 기대어 자식을 찾으면 사용 방식이 제한된다. 렌더링할 때마다 새 컴포넌트 함수를 만드는 방식은 컴포넌트 타입도 바뀌어 상태를 유지하기 어렵게 할 수 있다.
이 예시에서는 컴포넌트 타입을 고정하고 하나의 직접 자식에게 className과 onClick을 전달한다.
import { cloneElement, Fragment, type MouseEventHandler, type ReactElement } from 'react';
type ChildProps = {
className?: string;
onClick?: MouseEventHandler<HTMLElement>;
};
export function Slot({ children, className, onClick }: ChildProps & {
children: ReactElement<ChildProps>;
}) {
if (children.type === Fragment) {
throw new Error('Slot requires a child that accepts DOM props.');
}
const childProps = children.props;
return cloneElement(children, {
className: [childProps.className, className].filter(Boolean).join(' ') || undefined,
onClick: event => {
childProps.onClick?.(event);
if (!event.defaultPrevented) onClick?.(event);
},
});
}
클릭은 자식의 핸들러부터 실행한다. 거기서 기본 동작을 막았다면 Slot의 추가 처리도 생략한다. 자식과 부모에게 모두 핸들러가 있을 때 누가 먼저 실행되고, 어디서 멈출지를 정한 것이다. 라이브러리마다 같은 순서라고 가정할 수는 없으므로 인터페이스에 명시해야 한다.
<Slot className="button-style">
<a href="https://velog.io/">블로그로 이동</a>
</Slot>
cloneElement는 props를 얕게 병합한다. className 결합이나 이벤트 합성이 자동으로 완성되는 것은 아니다. 데이터 흐름도 추적하기 어려워질 수 있어, 단순한 경우에는 명시적 props나 render prop을 사용하는 편이 나을 수 있다. React cloneElement
이 코드가 다루지 않는 것
이 예시는 하나의 자식, className, onClick만 다룬다. 전달받은 속성을 DOM에 반영하지 않는 사용자 컴포넌트에는 효과가 없다. style·여러 이벤트·외부 ref까지 합치려면 각각의 규칙이 더 필요하다.
링크에 버튼 스타일을 입혀도 버튼의 disabled 동작이 생기지는 않는다. 링크는 이동, 버튼은 동작이라는 의미를 유지해야 한다. 범용 UI 라이브러리라면 직접 구현한 범위와 검증된 Slot 구현의 범위를 비교할 필요가 있다.
이 예시에서는 클래스 이름은 이어 붙이고, 클릭은 자식부터 실행하며, 자식이 취소하면 거기서 멈춘다. 그 규칙 위에서 링크는 링크로, 버튼은 버튼으로 렌더링하면서 공통 스타일을 쓸 수 있다. DOM을 하나 더 감싸는 대신 어느 자식에게 무엇을 넘길지 정하는 것. JSX를 직접 다뤄보고 싶었던 이유도 이런 작은 조립 방식에 있었다.
Assisted by AI
© uiwwsw · 본인 소유의 글·이미지에 대한 무단 복제·재게시를 허용하지 않습니다. 개인 열람·원문 링크 공유·검색 색인과 제한적인 미리보기는 허용합니다. 법령상 이용 예외, 기존 이용 허락, 코드·제삼자 자료의 기존 라이선스는 유지합니다. 콘텐츠 이용 안내