C.W.K.
Stream
Lesson 06 of 07 · published

조용히 죽은 패턴 — HOC, render prop, class

~12 min · history, anti-patterns, hooks

Level 0React 입문자
0 XP0/54 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
이 패턴들은 옛 코드베이스에서 보일 거야. 새 코드에선 쓰면 안 돼. 각각 왜 죽었는지 아는 게 사망 날짜 외우는 것보다 쓸모 있어.

Class 컴포넌트

원조 React 컴포넌트 모델. class Foo extends React.Component, 라이프사이클 메서드 (componentDidMount, componentWillUnmount, shouldComponentUpdate), this.state, this.setState().

왜 사라졌나: 라이프사이클 메서드가 관련 로직을 클래스 전체에 흩뿌림 (데이터 페칭이 mount 와 update 사이에 갈림), this 바인딩이 영구 footgun, 컴포넌트 간 stateful 로직 리팩토링이 고통. Hook (2018) 이 셋 다 해결.

2026년에 새 class 컴포넌트 안 써. 공식 React 문서가 더는 primary path 로 안 가르쳐. Class 있는 코드베이스 유지보수 중이면 — 괜찮 — 다만 새 파일은 hook 가진 함수 컴포넌트.

HOC (Higher-order component)

HOC = 컴포넌트 받아서 새 컴포넌트 반환하는 함수. 패턴 (withRouter, withRedux, connect(mapStateToProps)(Component)) 이 hook 전 시대의 stateful 로직 공유 방식. withFoo(withBar(withBaz(MyComponent))) 합성하고 prop 타입이 여전히 말 되길 기도.

왜 죽었나: 래핑이 불투명 (본인 컴포넌트가 magic 세 겹 안에 사라짐), prop 충돌이 조용, TypeScript 가 결과 추론 쉽지 않음, 래퍼 체인이 stack trace 고통. 커스텀 hook 이 본인이 뭘 소비하는지 명시적으로 만들어서 HOC 의 99% 대체.

Render prop

컴포넌트가 함수를 children (또는 다른 prop) 으로 받아서 state 와 함께 호출해 렌더하는 패턴. <Mouse>{(pos) => <p>{pos.x}, {pos.y}</p>}</Mouse>.

왜 죽었나: 같은 이유. 2017년엔 inventive 했지만 커스텀 hook (const pos = useMousePosition()) 이 더 깨끗, 타입 체크 잘 됨, JSX 가 콜백 안에 nesting 안 됨.

실제 코드베이스에서 여전히 볼 것

안 싸워도 되는 살아있는 예외 셋:

  1. React.memo — 기술적으로 HOC 지만 memoization 의 작은 opt-in, 로직 공유 패턴 아님. 여전히 사용 OK (Track 7 에서 언제 쓰는지).
  2. Suspense 가 컴포넌트 감싸기 — composition, 로직-공유 HOC 아님.
  3. Error boundary — React core 에서 여전히 class 기반 (error-boundary hook 안 나왔음). 평생 클래스 하나 쓰게 됨; Track 5 lesson 3 에 나옴.
옛 HOC 리라이트 중이면 hook 으로 리라이트. 번역은 거의 기계적: HOC 가 prop 으로 주입한 게 hook 이 반환하는 게 됨. 래핑이 사라짐. 래핑되어 있던 컴포넌트가 hook 을 직접 호출. 매번 더 깨끗.

Code

같은 feature — render prop vs 커스텀 hook·tsx
// 2018 — render prop
class Mouse extends React.Component<{ children: (p: {x:number;y:number}) => React.ReactNode }, {x:number;y:number}> {
  state = { x: 0, y: 0 };
  handle = (e: MouseEvent) => this.setState({ x: e.clientX, y: e.clientY });
  componentDidMount() { window.addEventListener("mousemove", this.handle); }
  componentWillUnmount() { window.removeEventListener("mousemove", this.handle); }
  render() { return this.props.children(this.state); }
}
// 사용 — 콜백 안의 JSX 가 태그 안에.
<Mouse>{(pos) => <p>{pos.x}, {pos.y}</p>}</Mouse>

// 2026 — 커스텀 hook (Track 3 lesson 4 가 제대로 짓는 법)
import { useEffect, useState } from "react";
function useMousePosition() {
  const [pos, setPos] = useState({ x: 0, y: 0 });
  useEffect(() => {
    const handle = (e: MouseEvent) => setPos({ x: e.clientX, y: e.clientY });
    window.addEventListener("mousemove", handle);
    return () => window.removeEventListener("mousemove", handle);
  }, []);
  return pos;
}
function Tracker() {
  const pos = useMousePosition();
  return <p>{pos.x}, {pos.y}</p>;
}

External links

Exercise

실제 repo 에서 HOC 패턴 찾아 (GitHub 에서 withRouter 또는 connect(mapStateToProps) 검색). 하나 골라서 커스텀 hook 으로 변환 스케치. Hook 이 뭘 반환? 호출 지점이 어떻게 바뀜? 진짜로 잃은 게 있나?
Hint
거의 항상: HOC 가 주입한 prop 이 hook 의 반환 값이 되고, 래핑되어 있던 컴포넌트가 hook 을 직접 호출. 잃는 건 래핑의 환상뿐 — 그게 feature, loss 가 아냐.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.