각각의 컴포넌트는 개별적으로 고유한 유효범위를 가지기 때문에 다른 컴포넌트의 데이터를 직접 참조할 수는 없다.
그럼 서로 다른 컴포넌트들이 데이터를 주고받으려면 어떻게 해야 할까? 이것은 컴포넌트들의 관계가 상/하위 관계인지 아닌지에 따라 달라진다.
상/하위 관계가 있는 경우
부모/자식의 형태를 띄는 상/하위 컴포넌트의 관계에서는 방향에 따라 두 가지를 사용한다.
상위 -> 하위 즉 부모가 자식에게 데이터를 전달할 때에는 사용자 정의 속성(props)을 통해서 값을 전달한다.
하위 -> 상위 즉 자식이 부모에게 데이터를 전달할 때에는 이벤트를 발생(emit) 시켜서 데이터를 전달한다.
상/하위 관계가 없는 경우
컴포넌트를 만들다 보면 대부분 상/하위 관계를 통해서 값을 전달할 수 있다. 하지만 이런 경우가 아니라면?
형제 관계의 경우는 직접 전달 할 수 없고 부모를 통해서 간접적으로 전달할 수 있다. 즉 하위 컴포넌트 A가 부모에게 알리고 다시 부모가 하위 컴포넌트 B에 전달하는 식이다.
그런 관계가 전혀 없을 때에는 Pinia와 같은 전역의 상태 관리 라이브러리를 사용한다.
Vue 2.x에서 사용하던 Event Bus는 3.x에서 deprecated 돼서 더 이상 사용되지 않는다. 대신 provide와 inject가 사용된다.
QuizItem
컴포넌트 구성
이번에 만들어볼 QuizItem은 상위 컴포넌트로부터 문제를 입력 받아서 자식 컴포넌트에서 문제를 풀고 결과를 상위 컴포넌트로 반환한다.
props 선언과 활용
props는 하위 컴포넌트에 사용자 정의의 속성을 선언하고 상위 컴포넌트에서 하위 컴포넌트 사용 시 속성 값을 전달하는 형식이다.
composition 방식에서 props를 선언할 때에는 defineProps라는 매크로를 이용한다. defineProps는 배열을 이용해서 여러개의 속성을 전달받을 수 있다. defineProps는 Proxy를 반환하는데 이 내부에 전달된 속성들 역시 Proxy 타입이다. 따라서 반응성이 유지된다.
<script> 영역에서 props에 접근하기 위해서는 definesProps의 반환값을 변수에 저장해서 사용하면 되고 <template>영역에서는 바로 접근하면 된다.
<script setup>
import { reactive } from 'vue'
import VariableTypeProps from './components/VariableTypeProps.vue'
import QuizItem from '@/components/QuizItem_1.vue'
// 문제 생성
const quizs = reactive([])
for (let i = 0; i < 4; i++) {
let num1 = parseInt(Math.random() * 9) + 1
let num2 = parseInt(Math.random() * 9) + 1
let oper = '+-*/'.charAt(parseInt(Math.random() * 3))
quizs.push(num1 + oper + num2)
}
</script>
<template>
<h1>여기는 AppQuiz_1.vue</h1>
<h2>컴포넌트간 통신</h2>
<QuizItem
v-for="(quiz, index) in quizs"
:key="index"
title="뭘까?"
v-bind:quiz="quiz"
myCustomProperty="가나?"
/>
<hr />
</template>
<style scoped></style>
props naming rule
Frontend framework들은 javascript와 HTML을 다루다 보니 두 언어의 naming rule을 넘나들어야 하는 점이 혼란스러울 때가 있다.
예를 들어 Component 이름은 JavaScript에서 작성하므로 Pascal case를 쓰는데 이것을 태그로 사용할 때는 html 영역이므로 kebab case를 사용한다. 하지만 그냥 사용하면 일반 태그와 헷갈리기 때문에 Pascal case를 그대로 사용하는 것을 허용하고 있다.
마찬가지 상황이 props에서도 발생하는데 props는 javascript 영역에서 선언하고 html 영역에서 사용한다.
<script> 또는 <template>의 표현식 내부: 이 영역에서는 props가 변수로 사용되어야 하기 때문에 javaScript의 naming rule에 부합되어야 한다. 따라서 camel case를 사용한다.