Programming Language/Vue
[Vue] 06)Data Processing
Sergemeow
2023. 5. 3. 14:44
Data in components
- 여러 components 사이에서 공유되어야할 데이터들이 있음
- 컴포넌트는 부모-자식 관계를 가지고 있으므로, 부모-자식 관계만 데이터를 주고받게 함
pass props & emit event
- 부모 ⇒ 자식으로의 데이터의 흐름
- pass props의 방식
- 자식 ⇒ 부모로의 데이터의 흐름
- emit event의 방식
Pass Props
- 요소의 속성(property)을 사용하여 데이터 전달
- props는 부모(상위) 컴포넌트의 정보를 전달하기 위한 사용자 지정 특성
- 자식(하위) 컴포넌트는 props 옵션을 사용하여 수신하는 props를 명시적으로 선언해야함
props in HelloWorld
- Vue CLI를 설치할 때 기본 생성 되는 App.vue의 HelloWorld 컴포넌트를 보면 msg라는 property가 작성되어 있음
- HelloWorld.vue에서 msg를 사용한 것을 확인 가능. App.vue에서 property로 넘긴 msg가 출력됨
- App.vue의 <HelloWorld/> 요소에 msg=”~”라는 property를 설정하였고, 하위 컴포넌트인 HelloWorld는 자신에게 부여된 msg property를 template에서 {{ msg }}의 형태로 사용한 것
Pass Props
- 위와 같이 부모⇒자식으로의 data 전달 방식을 pass props라고 함
- 정적인 데이터를 전달하는 경우 static props라고 명시하기도 함
- 요소에 속성을 작성하듯 사용 가능하여,
- 이 때 속성의 키 값은 kebab-case를 사용
- prop-data-name="value" 의 형태로 데이터를 전달
- Prop 명시
- 예)
<script> export default{ name: 'HelloWorld', props: { msg: String } } </script>
- 데이터를 받는 쪽, 즉 하위 컴포넌트에서도 props에 대해 명시적으로 작성 해주어야 함
- 전달받은 props를 type과 함께 명시
- 컴포넌트를 문서화할 뿐만 아니라, 잘못된 타입이 전달하는 경우 브라우저의 자바스크립트 콘솔에서 사용자에게 경고
MyComponent to MyChild
// MyComponent.vue
<template>
<div class='border'>
<h1>This is my component</h1>
<MyChild static-props="component에서 child로"/>
</div>
</template>
// MyChild.vue
<template>
<div>
<h3>This is child component</h3>
<p>{{ staticProps }}</p>
</div>
</template>
<script>
export default{
name: 'MyChild',
props: {
staticProps: String,
}
}
</script>
Pass Props convention
- 부모에서 넘겨주는 props
- kebab-case(HTML 속성명은 대소문자를 구분하지 않기 때문)
- 자식에서 받는 props
- camelCase
- 부모 템플릿(html)에서 kebab-case로 넘긴 변수를 자식의 스크립트(vue)에서 자동으로 camelCase로 변환하여 인식함
Dynamic props
- 변수를 props로 전달할 수 있음
- v-bind directive를 사용해 데이터를 동적으로 바인딩
- 부모 컴포넌트의 데이터가 업데이트 되면 자식 컴포넌트로 전달되는 데이터 또한 업데이트 됨
컴포넌트의 data 함수
- 각 vue 인스턴스는 같은 data 객체를 공유하므로 새로운 data 객체를 반환(return)하여 사용해야 함
Pass Props
- :dynamic-props=”dynamicProps”는 앞의 key값(dynamic-props)이란 이름으로 뒤의 “” 안에 오는 데이터(dynamicProps)를 전달하겠다는 뜻
- 즉, :my-props=”dynamicProps”로 데이터를 넘긴다면, 자식 컴포넌트에서 myProps로 데이터를 받아야함
- v-bind로 묶여있는 “ “안의 구문은 javascript의 구문으로 볼 수 있음
- 따라서 dynamicProps라고 하는 변수에 대한 data를 전달할 수 있는 것
- 숫자를 전달하기 위해서는… “1”, 문자는 “’1’”
단방향 데이터 흐름
- 모든 props는 부모에서 자식으로 즉 아래로 단방향 바인딩을 형성
- 부모 속성이 업데이트되면 자식으로 흐르지만 반대 방향은 아님
- 부모 컴포넌트가 업데이트될 때마다 자식 컴포넌트의 모든 prop들이 최신 값으로 새로고침 됨
- 목적
- 하위 컴포넌트가 실수로 상위 컴포넌트 상태를 변경하여 앱의 데이터 흐름을 이해하기 힘들게 만지는 것을 방지
- 하위 컴포넌트에서 prop를 변경하려고 시도해서는 안되며 그렇게하면 Vue는 콘솔에서 경고를 출력함
Emit Event
- 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달할 때는 이벤트를 발생시킴
- 이벤트를 발생시켜서 데이터를 전달하는 것
- 데이터를 이벤트 리스너의 콜백함수의 인자로 전달
- 상위 컴포넌트는 해당 이벤트를 통해 데이터를 받음
$emit
- $emit 메서드를 통해 부모 컴포넌트에 이벤트를 발생
- $emit(’event-name’) 형식으로 사용하며 부모 컴포넌트에 event-name이라는 이벤트를 발생했다는 것을 알림
- 마치 사용자가 마우스 클릭을 하면 click 이벤트가 발생하는 것처럼 $emit(’event-name’)가 실행되면 event-name 이벤트가 발생하는 것
- 자식 컴포넌트에 버튼을 만들고 클릭 이벤트를 추가
- $emit을 통해 부모 컴포넌트에게 child-to-parent 이벤트를 트리거
// MyChild.vue
<template>
<div>
...
<button @click="ChildToParent">클릭</button><br>
</div>
</template>
<script>
export default{
...
methods: {
ChildToPrevent: function(){
this.$emit('child-to-parent')
}
}
}
</script>
- emit된 이벤트를 상위 컴포넌트에서 청취 후 핸들러 함수 실행
// MyComponent.vue
<template>
<div class='border'>
<h1>This is my component</h1>
<MyChild
...
@child-to-parent = "parentGetEvent"
/>
</div>
</template>
<script>
export default{
...
methods: {
parentGetEvent: function(){
console.log("자식 컴포넌트에서 발생한 이벤트")
}
}
</script>
- 흐름 정리
- 자식 컴포넌트에 있는 버튼 클릭 이벤트를 청취하여 연결된 핸들러 함수(ChildToParent) 호출
- 호출된 함수에서 $emit을 통해 상위 컴포넌트에 이벤트(child-to-parent) 발생
- 상위 컴포넌트는 자식 컴포넌트가 발생시킨 이벤트(child-to-parent)를 청취하여 연결된 핸들러 함수(parentGetEvent) 호출
emit with data
- 이벤트를 발생(emit) 시킬 때 인자로 데이터를 전달 가능
// MyChild.vue
<script>
export default{
...
methods: {
ChildToParent: function(){
this.$emit('child-to-parent', 'child data')
}
}
}
</script>
- 이렇게 전달한 데이터는 이벤트와 연결된 부모 컴포넌트의 핸들러 함수의 인자로 사용 가능
// MyComponent.vue
<script>
export default{
...
methods:{
parentGetEvent:function(inputData){
console.log("자식 컴포넌트에서 발생한 이벤트!")
console.log("child component로부터 ${inputData}를 받음")
}
}
}
</script>
- emit with data 흐름 정리
- 자식 컴포넌트에 있는 버튼 클릭 이벤트를 청취하여 연결된 핸들러 함수(ChildToParent) 호출
- 호출된 함수에서 $emit을 통해 부모 컴포넌트에 이벤트(child-to-parent)를 발생
이벤트에 데이터(child data)를 함께 전달
- 부모 컴포넌트는 자식 컴포넌트의 이벤트(child-to-parent)를 청취하여 연결된 핸들러 함수(parentGetEvent) 호출, 함수의 인자로 전달된 데이터(child data)가 포함되어 있음
- 호출된 함수에서 console.log(’~child data~’) 실행
emit with dynamic data
- pass props와 마찬가지로 동적인 데이터도 전달 가능
- 자식 컴포넌트에서 입력받은 데이터를 부모 컴포넌트에게 전달하여 출력
// MyChild.vue
<template>
<div>
...
<input
type="text"
v-model="childInputData"
@keyup.enter="childInput"
>
</div>
</templtate>
// MyChild.vue
<script>
export detail{
...
data: function(){
return {
childInputData: null,
}
},
methods: {
childInput: function(){
this.$emit('child-input', this.childInputData)
this.childInputData = ""
},
}
</script>
// MyComponent.vue
<template>
<div class='border'>
<h1>This is my component</h1>
<MyChild
...
@child-input="getDynamicData"
/>
</div>
</template>
// MyComponent.vue
<template>
<div class = "border">
<h1>This is my component</h1>
<MyChild
...
@child-input="getDynamicData"
/>
</div>
</template>
// MyComponent.vue
<script>
import MyChild from '@/components/MyChild'
export default{
...
methods: {
getDynamicData: function(inputData){
console.log(`child component로부터 ${inputData}를 입력받음`)
}
}
}
</script>
emit with dynamic data 흐름 정리
- 자식 컴포넌트에 있는 keyup.enter 이벤트를 청취하여 연결된 핸들러 함수(ChildInput) 호출
- 호출된 함수에서 $emit을 통해 부모 컴포넌트에 이벤트(child-input)를 발생
- 이벤트에 v-model로 바인딩된 입력받은 데이터를 전달
- 상위 컴포넌트는 자식 컴포넌트의 이벤트(child-input)를 청취하여 연결된 핸들러 함수(getDynamicData) 호출, 함수의 인자로 전달된 데이터가 포함되어 있음
- 호출된 함수에서 console.log(~입력받은 데이터~) 실행
정리
- 자식 컴포넌트에서 부모 컴포넌트로 이벤트를 발생시킴
- 이벤트에 데이터를 담아 전달 가능
- 부모 컴포넌트에서는 자식 컴포넌트의 이벤트를 청취
- 전달받은 데이터는 이벤트 핸들러 함수의 인자로 사용
kebab-case VS camelCase
- HTML 요소에서 사용할 때는 kebab-case,
- JavaScript에서 사용할 때는 camelCase
- props하위에서 받을 때 JavaScript에서 받음: camelCase
- 상위 ⇒ 하위 흐름에서 HTML 요소로 내려줌 : kebab-case
- emit메서드, 변수명 등은 JavaScript에서 사용함: camelCase
- emit 이벤트를 발생시키면 HTML 요소가 이벤트를 청취함 : kebab-case
Lifecycle Hooks
- 각 Vue 인스턴스는 생성과 소멸의 과정 중 단계별 초기화 과정을 거침
- Vue 인스턴스가 생성된 경우, 인스턴스를 DOM에 마운트 하는 경우, 데이터가 변경되어 DOM을 업데이트 하는 경우 등
- 각 단계가 트리거가 되어 특정 로직을 실행할 수 있음
- 이를 Lifecycle Hooks라고 함
// components/ChildComponents.vue
export default{
...
beforeCreate(){
console.log('beforeCreate')
},
created(){
console.log('created')
},
beforeMount(){
console.log('beforeMount')
},
mounted(){
console.log('mounted')
}}
// components/ChildComponents.vue
export default{
...
data(){
return {
value: 0,
}
},
methods: {
changeValue(){
this.value = this.value + 1
}
},
beforeUpdate(){
console.log('beforeUpdate')
},
}
// App.vue
<template>
<div id="app>
<button @click="toggle">toggle</button>
<ChildComponent
v-if="flag"
/>
<hr>
</div>
</template>
<script>
export default{
data(){
return{
flag: true,
}
},
methods: {
toggle(){
this.flag =! this.flag
}
}
}
</script>
// components/ChildComponents/vue
<script>
export default{
...
beforeDestroy(){
console.log('beforeDestroy')
},
destroyed(){
console.log('destroyed')
}
}
</script>
created
- vue instance가 생성된 후 호출됨
- data, computed 등의 설정이 완료된 상태
- 서버에서 받은 데이터를 vue instance의 data에 할당하는 로직을 구현하기 적합
- 단, mount 되지 않아 요소에 접근할 수 없음
- JavaScript에서 학습한 Dog API 활용 실습의 경우 버튼을 누르면 강아지 사진을 보여줌
- 버튼을 누르지 않아도 첫 실행 시 기본 사진이 출력되도록 하고 싶다면, created 함수에 강아지 사진을 가져오는 함수를 추가
// components/DogComponent.vue
export default{
...
created(){
this.getDogImage()
},
mounted
- Vue instance가 요소에 mount된 후 호출됨
- mount된 요소를 조작할 수 있음
// components/DogComponent.vue
export default{
...
mounted(){
const button = document.querySelector('button')
button.innerText = '멍멍!'
}
- created의 경우, mount 되기 전이기 때문에 DOM에 접근할 수 없으므로 동작하지 않음
// components/DogComponent.vue
export default{
...
created(){
this.getDogImage()
const button = document.querySelector('button')
button.innerText = '멍멍!'
},
updated
- 데이터가 변경되어 DOM에 변화를 줄 때 호출됨
// components/DogComponent.vue
updated(){
console.log('새로운 멍멍이')
}
Lifecycle Hooks 특징
- instance마다 각각의 Lifecycle을 가지고 있음
// App.vue
export default {
...
created(){
console.log('App created!')
},
mounted(){
console.log('App mounted!')
},
}
// ChildComponent.vue
export default{
...
created(){
this.getDogImage()
console.log('Child created!')
},
mounted(){
const button = document.querySelector('button')
button.innerText = '멍멍!'
console.log('Child mounted!')
},
updated(){
console.log('새로운 멍멍')
console.log('Child updated!')
},
}
- Lifecycle Hooks는 컴포넌트별로 정의할 수 있음
- 현재 해당 프로잭트는
- App.vue 생성 ⇒ ChildComponent 생성 ⇒ ChildComponent 부착 ⇒ App.vue 부착 ⇒ ChildComponent 업데이트 순으로 동작한 것
- 부모 컴포넌트의 mounted hook이 실행되었다고 해서 자식이 mount 된 것이 아니고, 부모 컴포넌트가 updated hook이 실행되었다고 해서 자식이 updated 된 것이 아님
- 부착 여부가 부모-자식 관계에 따라 순서를 가지고 있지 않은 것
- instance 마다 각각의 Lifecycle을 가지고 있기 때문