Featuring Design System
Utils

mergeProps

여러 React props 객체를 안전하게 병합하는 유틸리티. 이벤트 핸들러는 체이닝, className은 연결, style은 병합합니다.

개요

mergePropsbase-ui에서 제공하는 유틸리티로, Featuring 패키지에서 re-export합니다. Object.assign이나 스프레드({...a, ...b})와 달리 이벤트 핸들러·className·style을 특별하게 처리합니다.

import { mergeProps } from '@featuring-corp/components';

언제 사용하나요

  • useRender에 내부 props와 소비자 props를 함께 넘길 때
  • behavior hook이 반환한 props를 컴포넌트 props와 합칠 때
  • render prop 콜백 내부에서 기존 props에 추가 속성을 덧붙일 때

언제 사용하면 안 되나요

  • ref를 병합할 때 — mergeProps는 ref를 병합하지 않습니다. useRenderref 배열 옵션을 사용하세요.
  • 단순히 마지막 값만 적용해도 되는 일반 객체 병합 — 스프레드로 충분합니다.

Usage

className 연결

스프레드는 className을 덮어씁니다. mergeProps는 두 값을 이어 붙입니다.

() => {
const merged = mergeProps(
  { className: 'base-class' },
  { className: 'override-class' },
);
// className: 'override-class base-class'
return (
  <Box $css={{ display: 'flex', flexDirection: 'column', gap: '$spacing-200' }}>
    <Box $css={{ color: '$text-3', fontSize: '14px' }}>
      스프레드: className = 'override-class' (base-class 유실)
    </Box>
    <Box $css={{ color: '$text-1', fontWeight: '600' }}>
      mergeProps: className = '{merged.className}'
    </Box>
  </Box>
);
}

이벤트 핸들러 체이닝

양쪽 핸들러가 모두 실행됩니다. 우측(나중에 전달된) 핸들러가 먼저 실행됩니다.

() => {
const [log, setLog] = useState([]);
const append = (msg) => setLog((prev) => [...prev.slice(-3), msg]);

const merged = mergeProps(
  { onClick: () => append('internal onClick') },
  { onClick: () => append('external onClick') },
);

return (
  <Box $css={{ display: 'flex', flexDirection: 'column', gap: '$spacing-300' }}>
    <Button.Root onClick={merged.onClick}>클릭해보세요</Button.Root>
    <Box $css={{ display: 'flex', flexDirection: 'column', gap: '$spacing-100' }}>
      {log.map((entry, i) => (
        <Box key={i} $css={{ color: '$text-2', fontSize: '13px' }}>{entry}</Box>
      ))}
    </Box>
  </Box>
);
}

style 병합

두 style 객체를 합치고, 충돌하는 속성은 우측이 이깁니다.

() => {
const merged = mergeProps(
  { style: { color: 'red', fontSize: 14 } },
  { style: { color: 'blue', padding: 8 } },
);
// { color: 'blue', fontSize: 14, padding: 8 }
return (
  <Box style={merged.style} $css={{ borderRadius: '$radius-200', bgColor: '$background-2' }}>
    color: blue, fontSize: 14, padding: 8
  </Box>
);
}

useRender와 함께 — 내부 props 안전하게 병합

useRender에 props를 전달할 때 mergeProps로 내부 props와 소비자 props를 합쳐 핸들러 유실을 막습니다.

import { useRender, mergeProps } from '@featuring-corp/components';
import type { UseRender } from '@featuring-corp/components';

interface LinkProps extends UseRender.ComponentProps<'a'> {
  variant?: 'primary' | 'secondary';
}

function Link({ render, variant = 'primary', ...externalProps }: LinkProps) {
  const internalProps = {
    className: `link link--${variant}`,
    onClick: () => console.log('link clicked'),
  };

  return useRender({
    defaultTagName: 'a',
    render,
    props: mergeProps<'a'>(internalProps, externalProps),
  });
}

// 소비자의 onClick이 내부 onClick을 덮어쓰지 않음
<Link variant="primary" onClick={() => analytics.track('link-click')}>
  링크
</Link>
// 클릭 시: analytics.track 실행 → console.log 실행

render prop 콜백 내부에서 props 확장

render prop 콜백에서 받은 props에 추가 속성을 붙일 때 사용합니다.

<Box
$css={{ padding: '$spacing-400', bgColor: '$background-2', borderRadius: '$radius-200' }}
render={(props) => (
  <nav
    {...mergeProps(props, {
      className: 'custom-nav',
      'aria-label': 'Main navigation',
    })}
  />
)}
>
Navigation content
</Box>

preventBaseUIHandler — 내부 핸들러 선택적 차단

외부 핸들러에서 base-ui 내부 핸들러만 선택적으로 막을 수 있습니다. preventDefault()와 달리 이벤트 전파에는 영향을 주지 않습니다.

<Button.Root
  render={(props) => (
    <button
      {...mergeProps<'button'>(props, {
        onClick(event) {
          if (isLocked) {
            event.preventBaseUIHandler();
          }
        },
      })}
    />
  )}
>
  Conditional Button
</Button.Root>

여러 props 소스 병합 (최대 5개)

mergeProps(
  defaultProps,    // 1. 기본값
  sprinklesProps,  // 2. 토큰 기반 스타일
  behaviorProps,   // 3. behavior hook 결과
  nativeProps,     // 4. 네이티브 HTML props
  consumerProps,   // 5. 소비자 전달 (최고 우선순위)
);

6개 이상이 필요하면 mergePropsN (배열 기반)을 사용합니다. 성능이 약간 낮으므로 5개 이하일 때는 mergeProps를 씁니다.

API

Prop

Type

반환값: 병합된 props 객체

병합 규칙 요약

props 종류병합 방식결과 예시
일반 props우측 우선 덮어쓰기id: 'b'
className연결 (우측이 앞)'override base'
style객체 병합 (우측 우선){ color: 'blue', fontSize: 14 }
이벤트 핸들러 (on*)체이닝 (우측 먼저 실행)양쪽 모두 실행
ref병합하지 않음 — useRenderref 배열 사용마지막 ref만 적용