Skip to content

Instantly share code, notes, and snippets.

@Phryxia
Last active February 13, 2025 06:07
Show Gist options
  • Select an option

  • Save Phryxia/6161fe461f705faebe1713b6a62c1fef to your computer and use it in GitHub Desktop.

Select an option

Save Phryxia/6161fe461f705faebe1713b6a62c1fef to your computer and use it in GitHub Desktop.
리액트 컴포넌트와 타입스크립트 다형성에 대한 주저리
type Big = { x: string }
type Small = Big & { y: string }
// 잘못된 다형적 컴포넌트
interface Props {
value: Big // Small도 Big이니까 넣어도 됨
onReport(newValue: Big): void // 이 코드의 의도는 (newValue: Small) => void도 전달받는 걸 상정함
}
function WrongPolymorphicComponent({ value, onChange }: Props) {
function modify(oldValue: Big) {
// 다형적이지 못함. y의 속성이 증발함.
// 하지만 타입은 문제가 없음
onReport({ x: oldValue.x + ' wow!' })
}
function create() {
onReport({ x: 'x' }) // y는?
}
}
// 사용처
function ComponentUsingSmall() {
const [small, setSmall] = useState<Small>({ /* 생략 */ })
return (
<WrongPolymorphicComponent
value={small}
onReport={setSmall} // 에러 안남
/>
)
}
@Phryxia
Copy link
Author

Phryxia commented Feb 13, 2025

그래서 어떻게 해야됨?

생성 로직의 외부주입

OOP의 Factory 패턴처럼 해당 컴포넌트의 책임을 넘어서는 부분의 생성을 호출자에게 전가하는 것이다.
얼핏 보면 바로 직전의 코드와 유사하다.

function foo(
  createBig: () => Big, 
  respond: (value: Big) => void
) {
  respond({
    ...createBig(),
    x: 'wanted x'
  })
}

하지만 실행흐름이 부모와 자식을 오고가며 난잡해진다는 단점이 있다.

이벤트 기반의 접근

딜레마에서 소개했던 코드는 지극히 정상적이다. 단지 적절한 이름과 관점이 필요했을 뿐이다.

function foo(
  onCreate: (base: Big) => void
) {
  onCreate({ x: 'wanted x' })
}

// 사용처에서 알아서 책임진다
foo((base: Big) => {
  const small = { ...base, y: 'for small' }
  // small을 알아서 씀
})

@Phryxia
Copy link
Author

Phryxia commented Feb 13, 2025

객체지향과의 질감적 차이

리액트 컴포넌트 프로그래밍을 함수형이라고 뭉개기엔 어폐가 있지만, 불변성과 행위를 주고받는다는 관점에서 함수형이라고 부르겠다.

  • 객체지향 패러다임에선 추상적인 관심사가 관념상 높은 곳에 위치하며, 상속(extend)받아서 쓴다.
  • 함수형 패러다임에선 추상적인 관심사가 관념상 깊은 곳에 위치하며, 합성(composite)하여 쓴다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment