컴포넌트를 분리하여 UI에 맞게 필요한 코드만 볼 수 있어서 좋아졌습니다. 그러나 코드가 분리되면서, 공용으로 사용하던 변수가 컴포넌트들이 따로 사용하게 되면서 일치하지 않는 문제가 발생했습니다.

이 값을 주고받을 수 있도록 컴포넌트 간의 통신이 필요합니다. 그러나 모든 컴포넌트가 서로 통신하게 되면 복잡해질 가능성이 있습니다.

따라서, 컴포넌트는 부모-자식 관계에 있는 컴포넌트끼리만 통신이 가능하도록 설계되었습니다. 이렇게 함으로써, 컴포넌트 간의 통신이 일어날 때 어디서부터 어디까지의 범위에서 일어나는지 명확하게 파악할 수 있으며, 컴포넌트 간의 의존성이 낮아져 코드를 재사용하거나 유지보수하기 쉬워집니다. 또한, 부모-자식 관계로 컴포넌트 간의 통신을 제한함으로써 코드의 복잡도를 낮출 수 있고, 앱의 성능을 향상시킬 수 있습니다.

그래서 MenuBar
컴포넌트와 PostList
컴포넌트가 같은 값을 들고 있으려면 부모 컴포넌트인 App
컴포넌트가 selectedCategory
값을 관리해줘야합니다.

부모 컴포넌트는 이 값을 자식 컴포넌트에 전해주기만 하면 됩니다. 이 때 사용하는 것이 prop
입니다.
prop
prop
은 property의 줄임말로, 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하기 위해 사용됩니다. 자식 컴포넌트를 사용할 때 속성처럼 값을 넣어서 전달합니다.
<MenuBar my-property="hello">
실제로 HTML에는 저런 속성이 없습니다. 그래서 이렇게 실제로는 해당 컴포넌트의 제일 상단 태그에 속성이 그대로 들어갑니다.

이 속성을 컴포넌트 데이터로써 전달하려면 자식 컴포넌트도 받을 준비를 해야합니다. props
속성에 받고자 하는 속성의 이름을 배열로 전달해주면 됩니다. 그리고 setup 함수의 첫 번째 전달인자를 통해 해당 값을 가져오게 됩니다.
실제로 HTML에는 이러한 속성이 없습니다. 따라서 해당 컴포넌트의 상단 태그가 하나만 있다면 자동으로 그 태그에 들어가게 됩니다.

이 속성을 컴포넌트 데이터로 전달하려면 자식 컴포넌트도 준비해야 합니다. props
속성에 받고자 하는 속성의 이름을 배열로 전달하고, setup 함수의 첫 번째 인자를 통해 해당 값을 가져올 수 있습니다.
export default {
props: ["myProperty"],
setup(props) {
console.log(props);
console.log(props.myProperty);
}
}
Proxy 객체를 전달 받았고 그 안에 myProperty
값이 들어있습니다.

만약 부모가 아무 속성도 넣지 않았다면 null
값이 나타납니다.
props
에 등록된 값은 setup 함수 뿐만 아니라 template에서도 사용할 수 있습니다.
export default {
props: ["myProperty"],
template: `
<div>{{ myProperty }}</div>
`,
}
Q] 왜 HTML에서는my-property
이고 Javascript에서는myProperty
인가요? A] 두 언어의 변수명 규칙이 다르기 때문입니다. HTML에서는 속성 값을 myProperty라고 넣어도 대소문자를 구분하지 않아서 myproperty라고 인식합니다. 그래서 하이픈(-
)으로 단어 구분 하는 kebab-case 기법을 사용합니다. 반면 Javascript는 변수명에 하이픈을 사용할 수 없기 때문에 중간 단어가 대문자인 camelCase 기법으로 변수명을 짓습니다. Vue는 이러한 변수명 기법 차이의 불편함을 해소하기 위해, 속성에 넣은 kebab-case를 자동으로 인식하고 camelCase로 변환해줍니다.
props 값 제한
props
는 자식이 음식 투정하는 것처럼, 특정한 타입의 값만 받도록 할 수 있습니다. 이를 위해 객체를 배열 대신 전달하고 추가적인 정보를 제공해야 합니다.
export default {
props: {
// 숫자만 받는다!
propA: Number,
// 문자열 또는 숫자만 받는다!
propB: [String, Number],
// 문자열로 받는데 이 값은 꼭 넣어줘라!
propC: {
type: String,
required: true,
},
// 숫자를 받는데 아무값도 안주면 100으로 들고 있겠다!
propD: {
type: Number,
default: 100,
},
},
};
만일 null
을 넣을 경우엔 배열처럼 어떤 제한도 없이 다 받을 수 있습니다. 객체든 함수든 말이죠..

v-bind를 써야할 순간
우린 단순히 hello
를 전달하려고 하는게 아닙니다. selectedCategory
가 가진 값을 postList에 전달해서 글 목록 타이틀로 보여줘야합니다. 그러니 다음과 같이 바꿔보겠습니다.

App.js에서 selectedCategory
만들어주고 이를 속성으로 전달하겠습니다. 속성명도 동일하게 하겠습니다.
import { ref } from "https://unpkg.com/vue@3/dist/vue.esm-browser.js";
import MenuBar from "./components/MenuBar.js";
import PostList from "./components/PostList.js";
export default {
template: `
<MenuBar />
<PostList :selected-category="selectedCategory" />
`,
setup() {
const selectedCategory = ref("HTML");
return {
selectedCategory,
};
},
components: {
MenuBar,
PostList,
},
};
그리고 postList에서 props
속성에 selectedCategory
를 추가합니다.
export default {
template: `
...
<li v-for="item in postList[selectedCategory]" :key="item.id">
...
`,
props: ["selectedCategory"],
setup() {
const postList = {
HTML: [
{ id: 0, title: "HTML 기본 1" },
{ id: 1, title: "HTML 기본 2" },
],
CSS: [{ id: 2, title: "CSS 기본 1" }],
};
return {
postList,
props,
};
},
};
어쨋든 글 목록이 상위 App 값에 잘 전달된 것 같습니다!! 그런데 클릭해도 아무런 변화가 없네요.

뭐야 그럼 이전이랑 다를게 없잖아요!

네 그렇지만 한 가지 보여드릴게 있습니다. 크롬 확장프로그램으로 깔았던 Vue devtools를 사용해볼 때입니다. 제일 우측에 있는 (없다면 >>
화살표를 클릭해서) Vue
를 클릭해주세요.

그러면 아래에 최상단 컴포넌트에서 setup에서 만든 변수들이 나열되어 있습니다. selectedCategory: "HTML" (Ref)
라는 항목을 볼 수 있습니다.
HTML을 더블클릭해서 “CSS"
나 “Javascript"
로 바꿔보세요!

최상위 컴포넌트의 selectedCategory
값이 변경되면, PostList에 전달된 값도 반응형이기 때문에 변경 사항이 반영됩니다.

그렇다는 것은 메뉴바의 클릭이 문제입니다. 왜냐하면 메뉴 바에서 클릭했을 때 바꾸어야할 selectedCategory
값은 부모 컴포넌트가 가지고 있기 때문입니다.

이렇게 자식 컴포넌트에서 이벤트가 발생하지만 처리는 부모 컴포넌트에서 수행해야 하는 상황이 발생할 수 있습니다. 이 경우, 이벤트를 전달하여 상위 컴포넌트가 처리할 수 있도록 해야합니다. 자식 컴포넌트가 부모 컴포넌트에 데이터를 전달한 것처럼, 자식 컴포넌트에서 부모 컴포넌트로 이벤트를 전달하는 방법이 있습니다. 다음 글에서 자세히 설명하겠습니다.