Utils
mergeProps
여러 React props 객체를 안전하게 병합하는 유틸리티. 이벤트 핸들러는 체이닝, className은 연결, style은 병합됩니다.
개요
mergeProps는 base-ui에서 제공하는 유틸리티로, 여러 React props 객체를 안전하게 병합합니다. Object.assign이나 스프레드({...a, ...b})와 달리, 이벤트 핸들러·className·style을 특별하게 처리합니다.
import { mergeProps } from '@featuring-corp/components';왜 mergeProps인가?
스프레드의 문제점
// ❌ 위험: onClick이 덮어쓰기됨
const merged = { ...internalProps, ...externalProps };
// externalProps.onClick만 실행됨, internalProps.onClick은 유실
// ❌ 위험: className이 덮어쓰기됨
const merged = { ...{ className: 'internal' }, ...{ className: 'external' } };
// className: 'external' — 'internal'은 유실mergeProps의 해결
// ✅ 안전: 양쪽 onClick 모두 실행
const merged = mergeProps(internalProps, externalProps);
// externalProps.onClick 먼저 실행 → internalProps.onClick 이어서 실행
// ✅ 안전: className이 연결됨
const merged = mergeProps({ className: 'internal' }, { className: 'external' });
// className: 'external internal'병합 규칙
일반 props — 우측 우선
mergeProps({ id: 'a', dir: 'ltr' }, { id: 'b' });
// → { id: 'b', dir: 'ltr' }className — 연결 (우측이 앞)
mergeProps({ className: 'base' }, { className: 'override' });
// → { className: 'override base' }
mergeProps(
{ className: 'a' },
{ className: 'b' },
{ className: 'c' },
);
// → { className: 'c b a' }style — 병합 (우측 우선)
mergeProps(
{ style: { color: 'red', fontSize: 14 } },
{ style: { color: 'blue', padding: 8 } },
);
// → { style: { color: 'blue', fontSize: 14, padding: 8 } }이벤트 핸들러 — 체이닝 (우측 먼저 실행)
mergeProps(
{ onClick: () => console.log('internal') },
{ onClick: () => console.log('external') },
);
// 클릭 시: 'external' 출력 → 'internal' 출력 (양쪽 모두 실행)ref — 병합하지 않음
ref는 mergeProps에서 병합되지 않습니다. ref 병합이 필요하면 useRender의 ref 옵션을 사용하세요.
// ❌ ref는 mergeProps로 병합하지 마세요
mergeProps({ ref: refA }, { ref: refB });
// refB만 적용됨
// ✅ useRender의 ref 옵션 사용
useRender({
ref: [refA, refB], // 양쪽 모두 적용
// ...
});Usage
컴포넌트 내부에서 props 병합
import { useRender, mergeProps } from '@featuring-corp/components';
function Button({ render, variant, ...externalProps }) {
const internalProps = {
className: `btn btn-${variant}`,
type: 'button' as const,
onClick: () => console.log('button clicked'),
};
return useRender({
defaultTagName: 'button',
render,
props: mergeProps<'button'>(internalProps, externalProps),
});
}
// 사용: 외부 onClick이 내부 onClick을 덮어쓰지 않음
<Button.Root variant="primary" onClick={() => analytics.track('click')}>
Submit
</Button.Root>
// 클릭 시: analytics.track 실행 → console.log 실행preventBaseUIHandler — 내부 핸들러 차단
외부에서 내부 핸들러의 실행을 선택적으로 차단할 수 있습니다:
<Button
render={(props) => (
<button
{...mergeProps<'button'>(props, {
onClick(event) {
if (isLocked) {
event.preventBaseUIHandler(); // 내부 onClick만 차단
// preventDefault()와 달리 다른 이벤트 전파에는 영향 없음
}
},
})}
/>
)}
>
Conditional Button
</Button.Root>함수형 props (지연 평가)
props 대신 함수를 전달하면, 이전까지 병합된 props를 인자로 받아 새 props를 반환합니다:
const merged = mergeProps(
{ onClick: handleA, className: 'base' },
(prevProps) => ({
// prevProps = { onClick: handleA, className: 'base' }
'aria-label': prevProps.className?.includes('base') ? 'Base Button' : 'Button',
}),
);여러 props 소스 병합 (최대 5개)
mergeProps(
defaultProps, // 1. 기본값
sprinklesProps, // 2. 토큰 기반 스타일
behaviorProps, // 3. behavior hook 결과
nativeProps, // 4. 네이티브 HTML props
consumerProps, // 5. 소비자 전달 (최고 우선순위)
);6개 이상이 필요하면 mergePropsN (배열 기반)을 사용할 수 있습니다. 단, 성능이 약간 낮으므로 5개 이하일 때는 mergeProps를 사용하세요.
실전: 디자인 시스템에서의 활용
render prop 콜백에서 사용
import { Box, mergeProps } from '@featuring-corp/components';
<Box
$css={{ padding: '$spacing-400', bgColor: '$background-2' }}
render={(props) => (
<nav
{...mergeProps(props, {
className: 'custom-nav',
'aria-label': 'Main navigation',
})}
/>
)}
>
Navigation content
</Box>behavior hook과 조합
import { useRender, mergeProps } from '@featuring-corp/components';
function useTooltipTrigger() {
return {
triggerRef: useRef(null),
getTriggerProps: () => ({
'aria-describedby': 'tooltip-1',
onMouseEnter: () => setOpen(true),
onMouseLeave: () => setOpen(false),
}),
};
}
function MyButton({ render, ...props }) {
const { triggerRef, getTriggerProps } = useTooltipTrigger();
return useRender({
defaultTagName: 'button',
render,
ref: [forwardedRef, triggerRef],
// behavior hook의 props가 안전하게 병합됨
props: mergeProps<'button'>(getTriggerProps(), props),
});
}API Reference
mergeProps
최대 5개의 props 객체(또는 함수)를 병합합니다.
Prop
Type
반환값: 병합된 props 객체
병합 규칙 요약
| props 종류 | 병합 방식 | 예시 |
|---|---|---|
| 일반 props | 우측 우선 덮어쓰기 | id: 'b' |
className | 연결 (우측이 앞) | 'external internal' |
style | 객체 병합 (우측 우선) | { color: 'blue', fontSize: 14 } |
이벤트 핸들러 (on*) | 체이닝 (우측 먼저 실행) | 양쪽 모두 실행 |
ref | 병합하지 않음 | 마지막 ref만 적용 |