들어가며
export default function Component() {
const [message, setMessage] = useState('hello')
return <div>{message}</div>
}
<template>
<div>{{ message }}</div>
</template>
<script setup>
const message = ref('hello')
</script>
<style scoped></style>

둘의 차이가 느껴지십니까..
아니오라고 답한 당신, 프론트할 자격이 없다.
함수형으로 때려박아주시는 우리의 react 님과 다르게, html, css, js의 양식을 한번에 때려박아주시는 vue님은 접근법 자체가 다르다.
사실 내부적으로 돌아가는 건 JS/TS이기 때문에 그렇게 어렵다고 느끼진 않았는데 한 파일에 400줄 가까이를 구겨넣는 걸 보고 잠시 어지러워졌었다.
아래는 읽는 거 자유..(본인이 놀라웠던 점이기 때문에)
리액트에선 죄다 짧게 분리하고 쳐내고 오히려 파일 구조에 미친 자들이 많았기 때문에,, 얼마나 그 내부 코드가 간단하고 명료한지? 였지만, 뷰에선 아니 그냥 뭐하러 그렇게 해? 그냥 한 파일에 다 넣고 거기서 관리해.... 물론 공용 컴포넌트는 뺍니다만 '광적으로(전 진짜 넥스트/리액트 하면서 집착의 끝을 봄,, 파일안에 10개의 컴포넌트가 있는데 각 컴포넌트도 하위 컴포넌트를 두개씩 끼고 있더라구요 ;; 컴포넌트가 하는 일: 컴포넌트 불러오기)' 리액트처럼 함수 기반으로 나눠지지 않을 뿐더러 (템플릿스크립트스타일이잇는데어찌능지처참을해요) vue같은 경우는 부분적으로 적용도 되는 미친 사기 능력 때문에 점진적 프레임워크?????라고 한단다.. 엄멤메
리액트는 개발자가 편하게 개발할 수 있게 도와드릴게요~ 이고,
뷰는 디자이너/퍼블리셔가 개발할 때 님들 이거 익숙하시죠? 근데 저희가 다 준비해놨어요 골라드세요?
개발자시라구요? 디자이너/퍼블리셔가 넘겨주신거 그냥 그대로 쓰시고~ 필요한거 뽑아드십쇼~
장단점이 𝒥ℴ𝓃𝓃𝒶 많으니 편한걸 쓰면 될 듯 하다 ㄱㅅ합니다 개발자계의 선조들이시여
그래서
네 회사에서 Vue를 쓰니까 공부를 해야겠죠!!!
회사 코드 + Vue 공식 문서 + 클로드 + 노션 띄워놓고 영역전개해서 공부했다 ..
내가 느낀 차이점은 차차 얘기해보기로 하고 오늘 얘기해보고 싶은건
제네릭 컴포넌트였다.
해당 공부하던 Vue 공식 문서는
https://ko.vuejs.org/guide/typescript/composition-api.html#typing-template-refs
요기를 보고 있었다
요주의 단어 '제네릭'이 분명 next에서는 강조되지 않았던 말이었기 때문에 꽤나 흥미를 느꼈는데,

교수님 진도가 너무 빨라요..
제네릭 컴포넌트를 모르면 이걸 이해 못하겠는데? 라는 생각이 들었다는 것이다.
제네릭 컴포넌트? 제네릭이 뭔데?
class MyGenericModal<T extends string | number> {
open(content: T): void
}
제네릭 컴포넌트는 T가 사용할 때 결정되는 구조예요.
(출처: 클로드)
나: T가 뭔데?
클로드: T는 그냥 타입을 담는 변수예요. 이름은 아무거나 써도 되는데 관례상 T를 씁니다.
나: <>가 거의 제네릭의 핵심같아 보이는데
클로드: 맞아요! <> 가 제네릭의 핵심 문법이에요.
// T 없이 → 문자열만 받을 수 있음
function wrap(value: string): string[] {
return [value]
}
// T 있이 → 뭐든 받을 수 있음
function wrap<T>(value: T): T[] {
return [value]
}
wrap('hello') // T = string으로 자동 결정
wrap(42) // T = number로 자동 결정
<T> 를 꺾쇠 안에 쓰는 순간 "T라는 타입 변수 쓸게" 라고 선언한 거라 별도 선언이 필요 없다.
React 하면서 useState<string>(''), useRef<HTMLDivElement>(null)
이런 거 당연히 보잖아요 우리
사실 제네릭을 이미 쓴 거다.
다만 컴포넌트 자체에 제네릭을 거는 경우가 React에서는 거의 없어서 낯설게 느껴지는 것뿐이다.
그냥 거의 하나 뭉쳐서 지내는 친구들과 같았기 때문에 ㅋㅋㅋㅋㅋ
그럼 문제가 되는 문장을 다시 한 번 살펴보자
참조된 컴포넌트가 제네릭 컴포넌트의 경우, InstanceType이 동작하지 않는다.
InstanceType이 왜 안 될까?
보통 Vue에서 자식 컴포넌트 ref 타입을 이렇게 잡는다.
const modal = ref<InstanceType<typeof MyModal>>()
근데 제네릭 컴포넌트에서는 이게 안 된다.
이유는 간단하다. InstanceType은 타입이 확정된 상태에서만 인스턴스를 뽑을 수 있는데, 제네릭 컴포넌트는 ContentType이 뭔지 아직 모르는 상태라 TypeScript가 open()의 인자 타입을 추론하지 못한다.
// TypeScript 입장
// "ContentType이 string이야? number야? 나 모르는데?"
// → open()의 타입 추론 실패
InstanceType<typeof MyGenericModal>
이렇게!
그래서 ComponentExposed를 쓴다
import type { ComponentExposed } from 'vue-component-type-helpers'
const modal = ref<ComponentExposed<typeof MyGenericModal>>()
vue-component-type-helpers 라이브러리가 제네릭이 확정 안 된 상태에서도
defineExpose한 것들을 정확하게 추론할 수 있도록 처리해준다.
결론 !
| 일반 컴포넌트 | 제네릭 컴포넌트 | |
| 타입 결정 시점 | 컴포넌트 작성 시 | 사용하는 시점 |
| ref 타입 잡는 법 | InstanceType<typeof MyModal> | ComponentExposed<typeof MyModal> |