static create 패턴 - 생성자 대신 정적 메서드로 객체 생성
언제 사용?
- 생성 로직이 복잡하거나 유효성 검사 필요
- 생성 실패 시 null/Result 반환 (생성자는 항상 인스턴스 반환)
- 팩토리 패턴, 싱글톤
class User {
private constructor(private readonly name: string) {}
// 유효성 검사 + 실패 시 null 반환
static create(name: string): User | null {
if (!name || name.length < 3) return null
return new User(name)
}
}
// 팩토리 패턴
class Shape {
static create(type: 'circle' | 'rect', size: number): Shape {
return type === 'circle' ? new Circle(size) : new Rectangle(size)
}
}
// 싱글톤
class Config {
private static instance: Config
private constructor() {}
static create() {
return Config.instance ??= new Config()
}
}
TIP
단순 초기화만 필요하면 일반 생성자가 더 직관적. 복잡한 생성 로직에만 사용.
React Children 재귀 순회
WARNING
가능하지만 비추천 - 암묵적 의존성, 타입 안전성 부족, 매 렌더 트리 순회, 예측 불가능
function traverseReactNode(children: ReactNode, callback, typeToMatch?) {
Children.forEach(children, (child) => {
if (!isValidElement(child)) return
if (child.type === Fragment) {
traverseReactNode(child.props.children, callback, typeToMatch)
return
}
if (child.type === typeToMatch) callback(child)
if (child.props?.children) {
traverseReactNode(child.props.children, callback, typeToMatch)
}
})
}
대안 - Compound Component
<Tabs.Root>
<Tabs.List>
<Tabs.Trigger value="a">A</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="a">Content</Tabs.Content>
</Tabs.Root>
React Children 재귀 순회 - 가능하지만 비추천 (496.md에 코드)
문제점:
- 암묵적 의존성: 중첩된 Tab이 동작하는지 사용자가 예측 불가
- 타입 안전성 부족: required prop 누락이 컴파일 타임에 안 잡힘
- 성능: 매 렌더마다 전체 트리 순회
// ❌ 마법처럼 동작 (예측 불가)
<Tabs>{/* 어디에 넣든 Tab 찾아줌 */}</Tabs>
// ✅ Compound Component
<Tabs.Root>
<Tabs.List><Tabs.Trigger value="a">A</Tabs.Trigger></Tabs.List>
<Tabs.Content value="a">Content</Tabs.Content>
</Tabs.Root>
React 팀도 2021년부터 Children API 사용 권장하지 않음: “Using Children is uncommon and can lead to fragile code”
라이브러리: react-children-utilities - deepMap, deepFind, deepFilter
React의 이중성: “선언형”이라면서 Children API로 명령형 트리 순회 제공. 역사적 이유 (2013년엔 Context API도 없었음).
React Children - props 보고 동적으로 래핑 여부 결정
const renderChildren = (children) => {
const elements = React.Children.toArray(children)
const hasLink = elements.some(
(el) => React.isValidElement(el) && el.props.url
)
return hasLink ? children : <ul>{children}</ul>
}
// toArray는 string, number도 포함 → isValidElement 체크 필수
전직 트리: Children API → cloneElement → “compound component가 낫지 않나…”
AsChild 패턴 (Radix UI)
컴포넌트 기능은 유지하면서 렌더링 요소를 변경:
<Button asChild>
<a href="/home">Link Button</a>
</Button>
핵심 구현 - Slot 컴포넌트:
const Slot = forwardRef(({ children, ...props }, ref) => {
if (!isValidElement(children)) return null
return cloneElement(children, {
...props,
...children.props,
ref: ref || children.ref,
})
})
자식 요소 타입에 따른 조건부 Props:
type ConditionalProps<T> = T extends ReactElement<any, 'a'>
? { href?: string; external?: boolean }
: T extends ReactElement<any, 'button'>
? { type?: 'button' | 'submit' }
: {}
// 사용
;<Anchor href="/home" external>
{' '}
{/* a 태그일 때만 href 허용 */}
<a>Link</a>
</Anchor>
활용: 디자인 시스템, 라우터 통합 (<Button asChild><NextLink /></Button>)
간단한 Redux 스타일 Store 구현
type Reducer<S, A> = (state: S, action: A) => S
type Listener<S> = (state: S) => void
interface Store<S, A> {
getState: () => S
subscribe: (listener: Listener<S>) => () => void
dispatch: (action: A) => A
}
function createStore<S, A>(
reducer: Reducer<S, A>,
preloadedState: S
): Store<S, A> {
let currentState = preloadedState
let listeners: Listener<S>[] = []
return {
getState: () => currentState,
subscribe: (listener) => {
listener(currentState)
listeners.push(listener)
return () => {
listeners = listeners.filter((l) => l !== listener)
}
},
dispatch: (action) => {
currentState = reducer(currentState, action)
listeners.forEach((l) => l(currentState))
return action
},
}
} Maybe Monad로 null 체크 체이닝
type Maybe<T> = T | null
const Maybe = {
of: <T>(value: T): Maybe<T> => (value != null ? value : null),
map: <T, U>(m: Maybe<T>, fn: (v: T) => U): Maybe<U> =>
m != null ? Maybe.of(fn(m)) : null,
}
// 사용 예: DOM 요소 찾아서 스크롤
Maybe.of(scrollElement.current)
.map((root) => root.querySelector(`#${id}`))
.map((target) => target.getClientRects()[0])
.map((rect) => {
root.scrollLeft = rect.left
})
Generator로 early return 패턴도 가능
function* handleScroll(id) {
const root = scrollElement.current
if (!root) return
const target = root.querySelector(`#${id}`)
if (!target) return
yield target.getClientRects()[0]?.left ?? 0
}