본문으로 바로가기
윤창원uiwwsw · 작은 우주

개발과 기술

React Slot 패턴 — 자식에게 스타일과 클릭을 전달하는 규칙

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 · 본인 소유의 글·이미지에 대한 무단 복제·재게시를 허용하지 않습니다. 개인 열람·원문 링크 공유·검색 색인과 제한적인 미리보기는 허용합니다. 법령상 이용 예외, 기존 이용 허락, 코드·제삼자 자료의 기존 라이선스는 유지합니다. 콘텐츠 이용 안내

무단 복제·재게시를 허용하지 않습니다. 콘텐츠 이용 안내

윤창원이 벨로그에 남긴 글을 이 작은 우주에도 모았습니다. 사진은 누르면 원본 크기로 볼 수 있습니다. 원문의 전체 서식 보기 ↗

모든 글 둘러보기 →