
하나의 컴포넌트로 여러 개의 동일한 인스턴스를 만들 수 있지만 각 인스턴스마다 조금 다른 기능이나 내용이 들어가게 하고 싶을 수 있습니다. 블로그로 보자면 글 목록이 하나의 예인데요. 레이아웃은 동일한데 제목과 설명글만 달라지게 해야합니다. 이를 위해 사용하는 것이 props
인데요. 어떻게 사용하는 지 자세히 알아보겠습니다.
props란?
prop은 property의 줄임말로, 컴포넌트에서 사용할 속성을 추가할 수 있습니다.
<BlogPost post-title="글 제목" desc="설명 설명" />
이렇게 속성을 추가하면 어떤 장점이 있는 걸까요? 바로 외부에서 컴포넌트에 데이터를 전달할 수 있게 됩니다. 하지만 아무 속성이나 모두 받는 것은 아닙니다. 해당 컴포넌트에서 받을 데이터들을 정의해주어야합니다.
컴포넌트에서 props 정의
props
라는 속성을 등록할 수 있습니다. 이 때 배열 또는 객체로 전달할 수 있습니다.
props 배열로 정의
문자열의 배열로 속성을 정의할 수 있습니다.
export default {
props: ['postTitle', 'desc']
}
보통 속성을 넣을 땐 중간에 하이픈(-)이 들어간 kebab-case을 사용하고, props를 정의하고 이를 사용할 때에는 중간에 대문자가 들어간 camelCase를 씁니다. HTML에서 쓰이냐, Javascript에서 쓰이냐의 차이 때문이죠. 컴포넌트의 경우는 가독성을 위해 중간과 첫 글자가 모두 대문자인 PascalCase를 사용합니다.
props 객체로 정의
객체는 다음과 같이 정의할 수 있는데 배열과 달리, 타입이나 조건을 통해 제한을 둘 수 있습니다.
export default {
props: {
postTitle: {
type: String, // 문자열로 제한
required: true // 필수로 넣어야함
},
desc: {
type: String, // 문자열로 제한
default: "" // 기본값 ""
},
}
}
대략 이 정도만 알아두고 자세한 건 아래에서 다루겠습니다.
Props 속성 전달하기
props를 정의했다면 이제 컴포넌트를 사용할 때 속성으로 전달하면 됩니다.
<BlogPost post-title="글 제목" desc="설명 설명" />
v-bind로 전달
문자열로 전달해도 되지만, v-bind도 사용 가능합니다.
<template>
<BlogPost :post-title="title" :desc="desc" />
</template>
<script>
export default {
data() {
return {
title: "글 제목",
desc: "설명 설명"
};
}
}
</script>
v-for + v-bind로 전달하기
맨 처음 예시를 들었던 블로그 글 목록을 만드려면 v-for와 같은 반복문이 필요하게 됩니다.
<template>
<BlogPost
v-for="post of posts"
:post-title="post.title"
:desc="post.desc"
:key="post.title"
/>
</template>
<script>
export default {
data() {
return {
posts: [
{
title: "햄이네 박사라네",
desc: "아이네의 모시꺵이 같은 겁니다."
},
// ...
]
};
}
}
</script>
props 사용하기
속성으로 전달된 props
는 컴포넌트의 데이터(data)처럼 사용할 수 있습니다.
<template>
<div class="post">
<h1> {{ postTitle }} </h1>
<p> {{ desc }} </p>
</div>
</template>
주의할 점은 데이터와 달리 외부에서 가져온 값이기 때문에 읽기 전용이라는 점입니다. 부모 컴포넌트에서 값을 바꾸면 자동으로 props에 전달된 값도 변경되지만 반대는 적용되지 않습니다. 그래서 값을 덮어쓰려고 한다면,
export default {
props: ["postTitle", "desc"],
mounted() {
// ❌ props 값은 새로 쓸 수 없음!
this.postTitle += "글쓴이 : vuelogger";
},
};
다음과 같은 에러를 만납니다.
만약 자식 컴포넌트에서 부모 컴포넌트의 데이터를 바꿀 수 있다면 상위 컴포넌트 데이터가 바뀌었을 때, 그것이 어디에서 바뀌었는지 찾기 어렵기 때문에 읽기 전용으로 한것입니다.
props 값을 가져와서 바꾸고 싶다면?
값을 바꾸어야하는 상황은 여러 가지가 있을 수 있습니다. 예를 들면 counter 초기값을 받는 용도가 될 수 있겠죠. 그러면 초기값을 받아 data에서 새로 만들면 됩니다.
export default {
props: ['initialCounter'],
data() {
return {
counter: this.initialCounter
}
}
}
이후initialCounter
값이 바뀌더라도, 이미counter
값은 생성되었으니 바뀌지 않습니다.
아니면 값을 가져와서 조금만 변경하고 싶을 수도 있습니다. 문자열을 받는데 공백을 제거한다거나 소문자로 바꾸는 등 말이죠.
export default {
props: ['size'],
computed: {
// size 값이 바뀔 때마다 업데이트 됩니다.
normalizedSize() {
return this.size.trim().toLowerCase()
}
}
}
만약 props로 배열이나 객체를 전달했다면 내부 값을 바꿀 수는 있습니다. 왜냐하면 배열이나 객체 자체는 변경할 수 없지만 내부 값은 참조하기 때문이죠.
<template>
<!-- 이 버튼을 누르면 나이가 점점 늘어나요!! -->
<button @click="user.age++">{{ user.age }}</button>
</template>
<script>
export default {
props: ["user"],
};
</script>
하지만 이 방법은 추천하지 않습니다. 위에서도 말씀드렸듯이 문제가 발생했을 때 원인을 찾기 어렵게 하는 요인이기 때문입니다. 자식 컴포넌트에서 변경이 필요한 일이 발생한다면 이를 부모에 전달해서 부모 쪽에서 값을 변경하도록 해야합니다. 이는 이벤트 전달(emit an event)을 통해 할 수 있는데 추후 다뤄보겠습니다.
props 값 제한하기
위에서 잠시 객체로 props를 선언해서 타입이나 필수인지 여부 등의 제한 사항을 두는 방법이 있다는 것을 알았습니다. 그 외에도 몇 가지가 더 가능한데요. 예시들을 보겠습니다.
export default {
props: {
// 타입 제한
propA: Number,
// 여러개 타입 제한
propB: [String, Number],
// type 키로 타입 제한
// 필수로 넣어줘야함을 명시
propC: {
type: String,
required: true // 기본적으로는 모든 prop은 선택사항
},
// 기본값 (default)
// 외부에서 값을 정해주지 않은 경우나 undefined인 경우
// default에 명시한 값을 사용
propD: {
type: Number,
default: 100 // 기본값이 없는 경우 undefined
// Boolean의 경우 false
},
// 객체나 배열 타입의 경우는 함수를 통해 반환해야함
propE: {
type: Object,
// default 함수에서 매개변수로 들어온 값을 가져올 수 있음
default(rawProps) {
return { message: 'hello' }
}
},
// 함수 타입인 경우 default에 넣어준 함수가 곧 default 함수가 됨
propG: {
type: Function,
// Default function이라는 글자를 반환하는 함수가 디폴트가 됨
default() {
return 'Default function'
}
}
// 커스텀으로 제한
propF: {
validator(value) {
return ['success', 'warning', 'danger'].includes(value)
}
},
}
}
Lifecycle hook을 보면 prop
이 data나 computed 등 보다 먼저 생성되기 때문에 default, validator 함수에서 컴포넌트의 데이터나 함수들을 사용할 수 없습니다.
커스텀 타입 검사
기본적인 타입은 다음과 같습니다.
String
Number
Boolean
Array
Object
Date
Function
Symbol
그런데 만약 어떤 클래스를 만들었고 이 클래스가 들어왔는지 검사하고 싶다면 이 또한 가능합니다. Person이라는 클래스를 만들었다고 할 때
class Person {
constructor(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
}
Person 클래스를 타입 넣듯이 넣어주면 됩니다.
export default {
props: {
author: Person
}
}
외부에서 들어온 author라는 값이 Person의 인스턴스인지 instanceof Person
로 확인할 겁니다.
값이 필요없는 Boolean 속성
속성들 중에는 disabled
, checked
와 같이 값을 넣어줄 필요없는 것들도 있습니다. prop
도 마찬가지로 Boolean 타입으로 정의했다면, 외부에서 사용할 때도 동일하게 해주면 됩니다.
export default {
props: {
disabled: Boolean
}
}
이 컴포넌트에 true 또는 false 값을 전달할 수도 있지만, 속성만 적거나 아무것도 안 적는 것도 동일하게 적용됩니다.
<!-- :disabled="true" 와 동일 -->
<MyComponent disabled />
<!-- :disabled="false" 와 동일 -->
<MyComponent />