Utils
mergeProps
여러 React props 객체를 안전하게 병합하는 유틸리티. 이벤트 핸들러는 체이닝, className은 연결, style은 병합합니다.
개요
mergeProps는 base-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를 병합하지 않습니다.useRender의ref배열 옵션을 사용하세요. - 단순히 마지막 값만 적용해도 되는 일반 객체 병합 — 스프레드로 충분합니다.
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 | 병합하지 않음 — useRender의 ref 배열 사용 | 마지막 ref만 적용 |