[vue] #6. 컴포넌트 통신

bien·2023년 9월 14일
0

vue

목록 보기
9/11

1. 프로퍼티 소개 (부모 => 자녀 통신)

우리는 지금까지 Vue 앱에 필요한 데이터를 앱 안에 저장해왔다. 그러나 특정 로직이 포함된 재사용 가능한 컴포넌트를 만들고자 하기 때문에, 외부에서 데이터를 받아와야 할 수 있다.

프로퍼티(props)

  • 같은 컴포넌트를 여러번 사용할 때 매번 다른 데이터로 설정하는 기능

컴포넌트에서 받고자 하는 프로퍼티의 속성을 Vue가 인식하도록 해야 한다. 이를 위해선 컴포넌트 파일로 가서 구성 객체로 간 다음 여기에 props 프로퍼티를 추가한다. 여기에 받고자 하는 프로퍼티를 지정하면 된다.

export default {
	porps: [
    	'name',
        'phoneNumber',
        'emainAddress'
    ]
}

props에서 프로퍼티로 정의한 것들은 데이터 프로퍼티와 마찬가지로 템플릿을 포함한 Vue 컴포넌트 전체에서 사용 가능하다.

  • this 키워드로 참조할 수도 있다.
  • html코드에서는 소문자 케밥 케이스 구문을 사용해야 한다.
    • (phone-number)
  • 컴포넌트가 받는 프로퍼티를 정의할 때는 카멜 케이스를 사용한다.
    • (phoneNumber)
  • date, computed(연산 프로퍼티)와 이름이 중복되어선 안된다.

이 코드를 통해 Vue에게 해당 컴포넌트는 name, phoneNumer, emailAddress 이렇게 3가지의 프로퍼티(커스텀 속성)를 받게 된다고 알려준 셈이다. 이들은 컴포넌트가 사용될 때 HTML 요소로 사용될 수 있다.


2. 프로퍼티 작동 방식 및 프로퍼티 변경하기

App.vue 탬플릿 내부에서 FriendContact.vue를 사용하는 경우, App.vue를 부모, FriendContact.vue를 자식이라 칭한다. 이러한 관계를 부모-자식 관계라 부르고, 부모에서 자식으로 통신할때 프로퍼티를 사용한다.

(일반적으로) 프로퍼티는 불변한다.

App.vue

<friend-contact
	name="Lee"
    phone-nuber="12345"
    emain-address="ab@ab.com"
    is-favorite="1"
    ></friend-contact>

FriendContact.vue

methods: {
	toggleFavorite() {
    	if (this.isFavorite === '1') {
        	this.isFavorite = '0';
        } else {
        	this.isFavorite = '1'
        }
    }
}

자식 컴포넌트에서 부모에서 전달해준 props를 수정하는 코드를 짜보자. 저장하면 오류가 뜬다.

error Upexpected mutation of "isFavorite" prop

프로퍼티는 불변해야 한다. 🧐 왜 변경해서는 안되는걸까?

Vue는 단방향 데이터 플로우(Unidirectional data flow) 라는 개념을 사용하기 때문이다. 용어가 어려워보이나 간단한 개념이다. App.vue에서 friend-contact로 전달된 데이터는 App.vue에서만 변경이 가능하다. 자식인 friend-contact에서는 데이터를 변경할 수 없다. 데이터는 자식으로 전달되는것만 가능하고, 전달된 데이터를 자식 vue에서 변경하는 것은 부모 vue에서 관리하는 데이터를 변경할 수 없다.

부모 데이터 변경

데이터 변경에는 2가지 방법이 있다.

  1. 부모에게 데이터를 변경하고 싶다고 알리는 것.
    • 부모가 자체적으로 데이터를 업데이트하고, 업데이트 된 데이터를 자식에게 되돌려 보낸다.
  2. 받은 데이터를 자식 내부의 초기 데이터로 취급해 자식 vue에서 직접 변경하는 것.
    • 다만 변경사항은 자식에게만 적용되고 부모에게는 데이터 변경이 적용되지 않는다.

2.초기 데이터 취급

export default {
	props: ["name", "phoneNumber", "emailAddress", "isFavorite"],
    data() {
    	return {
        	detailAreVisible: false,
            friend: {
            	name="Lee"
    			phone-nuber="12345"
    			emain-address="ab@ab.com"
            },
            **friendIsFavorite: this.isFavorite**
		};
    }
    
    
methods: {
	toggleFavorite() {
    	if (this.friendIsFavorite === '1') {
        	this.friendIsFavorite = '0';
        } else {
        	this.friendIsFavorite = '1'
        }
    }
}

이제 friendIsFavorite 프로퍼티를 변경해도 오류가 발생하지 않는다. friendIsFavorite는 받은 프로퍼티 값을 초깃값으로 사용하는 일반 데이터 프로퍼티이기 때문!


3. 프로퍼티 검증하기

지금까지는 프로퍼티를 일렬로 쭉 나열하는 방법을 사용했다. (가장 간단한 방법)

프로퍼티 값: 배열

export default {
	props: ['name', 'phoneNumber', 'emailAddress', 'isFavorite']
}

간단한 앱이나 컴포런트라면 이런 방법도 괜찮다. 그러나 더 복잡한 컴포넌트이거나 팀으로 작업하는 경우, 프로퍼티에 대한 정보를 더 많이 공유해야 할 수도 있다. 컴포넌트에 잘못된 데이터를 전달하거나 필요할 프로퍼티를 빼먹었을때, vue가 그것을 알게 할 수도 있다.

프로퍼티 값: 객체(타입)

정보를 가지는 객체를 통해, 프로퍼티가 어떤 값을 가지는지 알릴수도 있다.

export default {
	props: {
    	name: String,
        phoneNumber: String,
        emailAddress: String,
        isFavorite: String
    }
}       

프로퍼티 값: 객체(타입 + 기본값)

  • required: 해당 프로퍼티 필수 포함 여부 확인.
  • default:
    • required: false로 프로퍼티가 필수가 아닌 것으로 설정한 경우
    • 기본값 설정 가능.
export default {
	props: {
    	name: {
			type: String,
            required: true
		},
        phoneNumber: {
        	type: Stringm
            required: true
        },
        emailAddress: {
        	type: String,
            required: true
        },
        isFavorite: {
        	type: String,
            required: false,
            default: '0'
		}
     }
}

프로퍼티 값: 객체(검증자 Validator)

참 또는 거짓을 반환하는 함수를 갖는 검증자(validator)를 추가할 수도 있다. 프로퍼티에 제공되는 값을 가져와 유효성 검사 로직을 적용하고 유효한 값인지 확인한다.

isFavorite: {
	type: String,
    required: false,
    default: '0',
    validator: function(value) {
    	return value === '1' || value === '0';
    }
}

전달받은 값이 0 혹은 1인 경우 true를, 그 외의 값들은 false를 전달해 isFavorite의 값을 0 혹은 1로만 제한할 수 있다.

이처럼 프로퍼티와 관련된 정보를 자세히 선언해두면 뷰에서 프로퍼티와 관련된 오류 로그를 띄워줘서 훨씬 빠르게 문제점을 확인할 수 있다. (컴포넌트를 더 올바르게 사용할 수 있다.)

지원되는 프로퍼티 값

  • 문자열(String)
  • 숫자(Number)
  • 불리언(Boolean)
  • 배열(Array)
  • 객체(Object)
  • 날짜(Date)
  • 함수(Function)
  • 심볼(Symbol)
  • 생성자 함수(Date와 같은 내장된 함수 또는 커스텀 함수)도 타입이 될 수 있다.

4. 동적 프로퍼티 값 사용하기

FriendContact.vue

isFavorite: {
	type: Boolean,
    required: false,
    default: '0'
    }

Boolean으로 type을 정의하면 Validator를 생력할 수 있다. (Boolean에는 true, false만 입력 가능하므로)

App.vue

<friend-contact
	name = "Manuel Lorenz"
    phone-number = "123456789"
    email-address = "ab@ab"
    is-favorite="1"
    ></friend-contact>  

그러나 is-favorite의 값으로는 true, false를 할당할 수 없다. 항상 어떠한 텍스트 값만 받기 때문이다. 이럴때 v-bind를 이용할 수 있다.

<friend-contact
	...
    v-bind:is-favorite="true"
    // (= :is-favorite="true")
    // (= :is-favorite="1 == 1")
    ></friend-contact>  

v-bind

  • 다른 HTML 속성 바인딩처럼 프로퍼티도 바인딩 가능하다.
  • 문자열이 아닌 값이 필요한 프로퍼티가 있다면, 해당 값 전달에 v-bind(:)를 사용할 수 있다.
  • 뿐만 아니라 표현식, 메서드 호출도 가능하다.

v-for, v-if

  • 기본 html 요소에 사용했듯이 컴포넌트에도 v-for, v-if를 사용할 수 있다.
	<friend-contact
	v-for="friend in friends"
    :key="friend.id"
    :name="friend.name"
    :phone-number"friend.phone"
    :email-address="friend.email"
    :is-favorite="true" // 별도의 값을 바인딩하지 않고 초기값만 부여했다.
    ></friend-contact>
  • 커스텀 컴포넌트에 v-for를 사용하는 경우 반드시 키를 추가해야 한다.
  • 각 컴포넌트마다 각기 다른 데이터 전달을 위해 모든 프로퍼티 값들을 동적으로 바인딩한다.

📌 point

  • v-for 사용 가능
  • 프로퍼티 동적 값 바인딩
  • 재사용 컴포넌트.

5. 커스텀 이벤트 방출 (부모 => 자식 통신)

App.vue에서 FriendContact.vue로 데이터를 전달하기 위해 프로퍼티(props)를 사용했다. 🧐 이와는 반대로 컴포넌트에서 부모로의 통신은 어떻게 이루어질까?

지금가지 우리가 구현한 것처럼, 컴포넌트 내부에서만 데이터가 변경 가능한것은 의미가 없다. 데이터베이스에서 정보를 가져와 컴포넌트에 주입하고, 컴포넌트 내부의 데이터 변경 또한 데이터베이스에 반영되어야 한다!

이를 위해 App -> FriendContact 통신을 의미하는 props와 반대 방향의 통신이 필요하다.

일반 html에서는 버튼을 클릭하면 버튼이 클릭됐다는 것을 알려준다. 버튼이 우리가 수신할 수 있는 클릭 이벤트를 발생시킨다. vue의 컴포넌트도 마찬가지이다. 컴포넌트가 부모에게 무언가 발생했음을 알리려면, 컴포넌트는 부모가 수신할 이벤트를 발생시켜야 한다.

FriendContact.vue

<template>
	<li>
    	...
    	<button @click="toggleFavorite">Toggle Favortie</button>
        ...
    </li>
</template>

export default {
	methods: {
    	toggleFavorite() {
        	this.$emit('toggle-favorite', this.id);
        }
    }
}

$emit

  • Vue와 함게 제공되는 내장 메서드
  • 부모 컴포넌트에서 수신할 수 있는 커스텀 이벤트를 발생시킨다.
  • 최소한 하나의 커스텀 이벤트의 이름 파라미터가 요구됨.
    • 케밥 케이스로 작성하는것이 관습
    • 마음대로 지으면 된다.
  • 부모 입장에서 여러개의 자식 컴포넌트가 있으니, 어느 컴포넌트인지 구분이 필요하다.
    • 2개 이상의 파라미터를 전달하면서 자식 컴포넌트 구분자를 전달할 수 있다!
    • 커스텀 이벤트의 이름 외 추가되는 파라미터는 이벤트와 함께 전달될 데이터이다.
    • 추가된 파라미터는 이벤트 수신 메서드의 첫번째 파라미터로 제공된다.

App.vue

	<friend-contact
    	v-for="friend in friends"
        :key="friend.id"
        ...
        :is-favorite="friend.isFavorite"
        @toggle-favorite="toggleFavoriteStatus()"
        >
    ...

export default {
	methods: {
    	toggleFavoriteStatus(firendId) {
        	const identifiedFriend this.friends.find(friend => friend.id === friendId);
            idenfitiedFriend.isFavorite = !identifiedFriend.isFavorite;
            
        }
    }	
};

v-on(@)

  • 부모 컴포넌트에서 이벤트 수신에는 v-on(@)를 사용할 수 있다.
  • 이벤트 발생 시 실행되어야 할 JavaScript 코드를 바인딩하면 된다.

데이터가 변경되면 vue가 해당 변경상태를 감지하고 업데이트된 즐겨찾기 상태를 자동으로 컴포넌트로 돌려보내 UI를 업데이트 해준다. 따라서 이제 자식 컴포넌트(FriendContact.vue)내부가 아닌 부모 컴포넌트(App.vue) 내부에서 즐겨찾기 상태를 변경할 수 있게 되었다.

이제 단방향 데이터 플로우에서 양방향으로 사용하게 되었다!


6. 커스텀 이벤트 정의 및 검증하기

이처럼 컴포넌트 통신에 있어 커스텀 이벤트는 중요한 역할을 수행한다. 프로퍼티처럼 이를 정의할 수도 있다. 이를 통해 컴포넌트가 발생시킬 이벤트를 Vue에 알려줄수도 있다. 프로퍼티에는 필수적으로 알려야 하고, 이벤트에는 필수적이지는 않으나 권장된다.

emits: 간단한 정의

export default {
	props: {...},
	emits: ['toggle-favorite'],
    }

emits은 props에 대응한다.
props에서는 컴포넌트가 수신하는 프로퍼티를 정의했고
emits에서는 컴포넌트가 어떠한 시점에 발생시킬 커스텀 이벤트를 정의한다.

컴포넌트에 이처럼 emits에 대해 적어두면 컴포넌트의 작동 방식과 어떤 이벤트를 수신하는지 등을 다른 개발자가 명확히 알 수 있다. (컴포넌트 이해를 돕는다.)

emits: 객체

export default {
	props: {...},
    emits: {
    	'toggle-favorite': function(id) {
        	if(id) {
            	return truen;
            } else {
            	console.warn('Id is missing!');
                return false
            }
        }

props에서 했던 것 처럼 객체를 지정할 수도 있다. 발생할 이벤트를 키로 추가해서 더 자세한 구성(함수)을 제공했다. 뿐만 아니라 필수적인 인수를 포함해서, 인수를 포함하지 않으면 에러를 띄우도록 코드를 구현했다.

이같은 커스텀 유효성 검사는 필수적이지는 않으나 여러명이 작업할때 개발을 도울 수 있다.


7. 프로퍼티/ 이벤트 폴스루 및 모든 프로퍼티 바인딩하기


8. 데모: 컴포넌트 추가 및 연결하기

지금까지 컴포넌트, emit 등 다양한 개념을 학습했다. 새로운 친구를 추가하는 컴포넌트를 추가하면서 지금까지 배운것들을 하나하나 되짚어보자!

components > NewFriend.vue

<template>
	<form>
      <div>
          <label>Name</label>
          <input type="text" />
      </div>
      <div>
          <label>Phone</label>
          <input type="tel" />
      </div>
      <div>
          <label>E-Mail</label>
          <input type="email" />
      </div>
      <div>
      	<button>Add contact</button>
      </div>
</template>

<sript>
	export default {};
</script>

새로운 컴포넌트 양식을 하나 추가해보았다. 이제 여기에 로직을 추가해야 한다.

main.js에 새로운 컴포넌트 추가

import {createApp} from 'vue';

import App from './App.vue';
import FriendContact from './components/FriendContact.vue';
import NewFriend from './components/NewFirend.vue';

const app = createApp(App);

app.component('frined-contact', FriendContact);
app.component('new-friend', NewFriend); // (-)가 들어간 두단어로 작명

app.mount('#app');
  • html에서는 소문자 케밥 케이스 구문을 사용한다.
    • phone-number
  • 컴포넌트가 받는 프로퍼티 정의시 카멜 케이스를 사용한다.
    • phoneNumber

부모 컴포넌트에 자식 컴포넌트 기록

App.vue

<template>
	<section>
    	<header>
        	<h1>My Firends</h1>
        </header>
        <new-friend></new-friend>
        <ul>
        	<friend-contact
            	v-for="friend in friends"
                :key="friend.id"
                :id="friend.id"
                :name="friend.name"
                ...

props?

이 NewFriend에는 어떤 props가 필요할까?

NewFriend 컴포넌트는 사용자 데이터 몇 가지를 수집해서 App.vue에 전달하는 역할을 한다. (자식 -> 부모) props는 부모에서 자식에게 데이터를 전달할때 사용되는 기능(외부로부터 데이터를 받기 위한 기능)이므로, NewFriend에는 해당 기능이 필요없다.

NewFriend.vue에 로직 추가

<template>
	<form @submit.prevent="submitData">
      <div>
          <label>Name</label>
          <input type="text" v-model:"enteredName" />
      </div>
      <div>
          <label>Phone</label>
          <input type="tel" v-model:"enteredPhone" />
      </div>
      <div>
          <label>E-Mail</label>
          <input type="email" v-model:"enteredEmail" />
      </div>
      <div>
      	<button>Add contact</button>
      </div>
</template>

<sript>
	export default {
    	emits: ['add-contact']
        data() {
        	return {
            	enteredName: '',
                enteredPhone: '',
                enteredEmail: ''
            };
        },
        methods: {
        	submitData() {
        		this.#emit('add-contact',
                			this.enteredName,
                            this.enteredPhone,
                            this.enteredEmail
                );
            }
        }
    };
</script>

emits

그러나 emits 이벤트는 반드시 필요하다! emits는 컴포넌트간의 통신에 있어서 발생시킬 커스텀 이벤트를 의미한다. 앞서 최소한 하나의 파라미터(커스텀 이벤트의 이름)가 요구된다고 언급되었다.(발생시킬 이벤트를 직관적으로 표현할 수 있도록 명명하자)

data

데이터 프로퍼티를 추가하여 이 컴포넌트에 필요한 고유 데이터를 관리하게 한다.

data와 html 바인딩

선언한 고유 데이터 프로퍼티를 v-model로 html 요소들과 바인딩한다.

@submit.prevent="submitData()"

@submit으로 양식 제출 이벤트를 수신한다. .prevent 수식어를 통해 제출할 때 페이지가 새로 고쳐지게 만드는 브라우저 기본 돚악을 방지한다.

submitData()

커스텀 이벤트 수신 대상(App.vue)에 해당 데이터 전달가능하도록 add-contact이벤트를 발생시민다.

this.$emit(커스텀 이벤트명, 전달할 데이터..)

  • emit으로 부모 컴포넌트와 통신하도록 구현했다.
  • 하나의 객체로 묶어서 데이터를 전달할 수도 있으나, 각각 세개의 인수로 데이터를 전달했다.

커스텀 이벤트 수신

App.vue

<template>
	<section>
    	<header>
        	<h1>My Firends</h1>
        </header>
        <new-friend @add-contact="addContact"></new-friend>
        <ul>
        	<friend-contact
            	v-for="friend in friends"
                :key="friend.id"
                :id="friend.id"
                :name="friend.name"
                ...
                
...

methods: {
	addContact(name, phone, email) {
    	const newFriendContact = {
        	id: new Date().toISOString(),
            name: name,
            phone: phone,
            email(프로퍼티명은 참조할 프로퍼티명이랑 같아야함): email, // 꼭 동일할 필요 없음.
            isFavorite: false
        };
        this.friends.push(newFriendContact);
    }
}		

@add-contact="addContact"

  • addContact는 꼭 이벤트에 썼던 이름과 동일할 필요는 없다. 마음대로 지정해도 된다. 둘이 달라도 된다.

addContact(name, phone, email)

  • 매개변수 이름도 마음대로 지어도 된다.
  • 단, 순서는 바뀌어선 안 된다. 이벤트 발생 시 정한 순서와 같아야 한다.

profile
Good Luck!

0개의 댓글