[Vue.js] Props, Emit 부모·자식 컴포넌트 데이터 전달(사용법)

토끼는 개발개발·2023년 8월 31일
0

Vue.js

목록 보기
12/19
post-thumbnail
post-custom-banner

📌 1. Props

1-1. props란?

컴포넌트에 등록할 수 있는 사용자 정의 속성.
간단히 말하면, 자식 컴포넌트에서 props를 선언하면 부모 컴포넌트에서 데이터(속성)를 전달할 수 있다.

위의 그림을 통해 직관적으로 알 수 있다.
부모 컴포넌트의 데이터를 자식 컴포넌트로 전달시키는 역할이다.

쉽게 예시 먼저 살펴보자.

<!-- 부모 컴포넌트 TheView.vue-->
<template>
  <AppCard title="제목1" contents="내용1">		     </AppCard>
</template>
<!--자식 컴포넌트 AppCard.vue-->
<template>
  <p> {{ title }} </p> <!--제목1--> 
  <p> {{ contents }} </p> <!--내용1-->
</template>
<script setup>
  export default {
    props: ['title', 'contents'],
    setup() {
      return {}
    }
  }
</script>

부모 컴포넌트 TheView.vue에서 자식 컴포넌트인 AppCard.vue에 title, contents 데이터를 전달했고,
자식 컴포넌트인 AppCard.vue에서 props를 선언하여 전달 받았다.

이처럼, Props는 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달할 때 쓰인다.



1-2. Props 객체문법 선언(추천!)

1-1처럼 문자열 배열을 사용하여 props를 선언할 수도 있지만, 객체 문법을 사용하여 속성 타입과 함께 선언할 수도 있다.

//자식 컴포넌트 AppCard.vue
export default {
  props: {
    title: {
      type: String,
      default: 'news'
    },
    contents: {
      type: String,
      required: true 
    }
  },
  setup(){
  	return {}
  }
}

데이터의 타입과 옵션을 설정해주므로 더욱 추천되는 방법이다.

  • type : String, Number, ... 모든 기본 생성자 또는 모든 사용자 정의 타입이 올 수 있다. 배열을 이용하여 여러개의 타입을 선언할 수도 있다. (ex. [Number, String])
  • defalut : 속성값이 비어있거나 undefined를 전달 받는 경우 기본값을 선언할 수 있다.
  • required : 속성이 필수값이라면 true로 해서 설정할 수 있다.
  • validator : 속성값의 유효성 검사가 필요할 때 사용할 수 있다.

❗ 컴포넌트 사용시 명시된 사항을 위반할 때 개발모드에서 콘솔 경고가 발생된다.


물론, 부모 컴포넌트에서도 데이터를 보낼때도 객체로 보낼 수 있다.

<template>
  <AppCard :title="post.title" :contents="post.contents"></AppCard>
</template>
<script setup>
const title = ref("제목1");
const contents = ref("내용1");
</script>

v-bind:title="post.title" 를 간단히 :title="post.title" 로 쓸 수 있다.



1.3 Props 단방향 데이터 흐름

모든 props는 상위 속성과 하위 속성간에 단방향 바인딩으로 형성되어 있다.

상위 속성 업데이트 -> 하위 속성 업데이트 (o)
하위 속성 업데이트 -> 상위 속성 업데이트 (x)

이러한 성질은 하위 속성 변경 실수로 상위 속성을 변경하여 데이터 흐름을 이해하기 어렵게 만드는 것을 방지할 수 있다.

또한, 상위 컴포넌트가 업데이트될 때마다 하위 컴포넌트의 모든 props 의 최신 상태도 초기화 된다.
그렇기 때문에 자식 컴포넌트 내부에서 props를 변경하지 않아야 한다.

//자식 컴포넌트 AppCard.vue
export default {
  props: {
    title: {
      type: String,
      default: 'news'
    },
    contents: {
      type: String,
      required: true 
    }
  },
  setup(props){
    props.title = "제목2"; //불가능. 반영되지 않으며 콘솔창 경고 출력
  	return {}
  }
}

부모로부터 전달받은 props.title을 자식 컴포넌트에서 변경할 수 없다.

그럼 다른 방법이 없을까?
그것은 자식컴포넌트에서 부모컴포넌트로 이벤트를 보내 부모컴포넌트에서 변경 이벤트를 발생시키는 것이다.

emits에 대해 알아보자.




📌 2. emits

emit을 이용하면 자식 컴포넌트에서 부모 컴포넌트로 이벤트를 내보낼 수 있다.

이벤트를 내보낸다는게 도대체 무슨말이냐면,

(1) 부모 컴포넌트에서 전달받은 데이터 title을 자식 컴포넌트에서 변경할 수 없다.
(2) 자식 컴포넌트에서 부모 컴포넌트로 이벤트를 보낼 수 있다. (emit)
(3) 그렇다면 데이터 title을 변경해야 되면, 자식 컴포넌트에서 부모 컴포넌트로 changeTitle 이벤트를 내보내겠다.
(4) changeTitle 이벤트를 받은 부모 컴포넌트는 해당 이벤트를 실행하여 title을 변경한다.

한마디로, 자식 컴포넌트에서 변경할 수 없으니 이벤트를 부모로 보내서 부모에서 변경을 실행하겠다는 것이다.

사용법은 예시를 살펴보자.

//자식 컴포넌트 AppCard.vue
export default {
  props: {
    title: {
      type: String,
      default: 'news'
    },
    contents: {
      type: String,
      required: true 
    }
  },
  emits: ['changeTitle'],
  setup(props, context){
    const buttonEvent = () => {
	    context.emit('changeTitle');
    }
  	return {buttonEvent}
  }
}

props와 마찬가지로 emits를 선언해주어야 한다.
changeTitle이벤트를 보냈다.

부모에서 이벤트를 받아보자.

<template>
  <AppCard 
       :title="post.title" 	
       :contents="post.contents"
       @changeTitle = "post.title = '제목2'">
  </AppCard>
</template>

자식에서 부모로 이벤트를 보낼 때, 부모는 @로 받는다.
부모에서 보낼 때는 : , 부모에서 받을 때는 @이다.

부모와 자식간의 데이터 전달은 단방향으로 이루어지는 것이다.

props : 부모 -> 자식
emits : 자식 -> 부모




📌 3. 객체/배열 Props 업데이트

Javascript 특성상 객체나 배열이 props로 전달되면 자식 컴포넌트에서는 prop 바인딩(값 변경)을 변경할 수 없지만 객체 또는 배열의 중첩 속성은 변경할 수 있다.

이건 정말정말 중요한 내용이다!!!

왜냐면, 많은 개발자들이 "어?? 되네??"하고 시도했다가 후에 데이터 흐름 추론이나 데이터 업데이트가 꼬여 골치아파지기 때문이다. (내 경험..)

중첩 속성은 변경할 수 있다는게 무슨 말이냐면,
자식 컴포넌트에서 props.title은 변경할 수 없었지만, props.post.title은 변경할 수 있다.

예시부터 보자.

위에서 처음에는 이렇게 보냈었다.

<!--부모 컴포넌트 -->
<template>
  <AppCard 
       :title="post.title">
  </AppCard>
</template>

그런데, 보낼 때 post 객체 자체를 보낸다고 생각해보자.

<!--부모 컴포넌트 -->
<template>
  <AppCard 
       :post="post">
  </AppCard>
</template>
<script>
  export default {
	setup(){
    	const post = reactive({
        	title: "제목"
        });
      
      	return { post }
    }
  }
</script>

자식에서는 이렇게 받을 것이다.

//자식 컴포넌트 AppCard.vue
export default {
  props: {
    post: {
      type: Object,
      default: () => ({})
    },
  },
  setup(props){
    const buttonEvent = () => {
	  //props.title = "제목2"; 불가능
      props.post.title = "제목2"; // 가능
    }
  	return {buttonEvent}
  }
}

props.title은 변경하지 못했는데, props.post.title은 변경할 수 있다. 객체 또는 배열의 중첩 속성은 변경할 수 있기 때문이다.

이것은 JavaScript에서 객체와 배열이 참조 타입(Reference Type)으로 전달되고 Vue가 이러한 변경을 방지하는것은 부당한 비용이 들기 때문에 가능하게 된 것이다.

이러한 변경은 데이터 흐름 추론에 어려움을 발생시키며, 부모에서 데이터를 업데이트 했을때 예상치 못한 문제가 발생할 수 있다.

(부모로부터 받은 데이터가 초기화만 되고, 업데이트 되지 않기를 바란다면 json.stringfy를 이용할 수 있다.)




참고문헌

Vue3 완전정복 - 짐코딩
Vue3 공식문서


팝업 호출, 컴포넌트 재사용 등에서 가장 중요한 props와 emits에 대한 내용이다.

script setup에서는 context가 아닌 defineProps, defineEmits를 사용한다.

이것에 대한 건 추후 script setup을 포스팅할 때 작성하도록 하겠다.

profile
하이 이것은 나의 깨지고 부서지는 기록들입니다
post-custom-banner

1개의 댓글

comment-user-thumbnail
2024년 3월 6일

3번 내용 많은 도움 되었어요 감사합니다~

답글 달기