[Vue] Component State Flow

young-gue Park·2023년 11월 3일
0

Vue.js

목록 보기
6/10
post-thumbnail

⚡ Component State Flow


📌 Passing Props

🔷 부모는 자식에게 데이터를 전달(Pass Props)하며, 자식은 자신에게 일어난 일을 부모에게 알림(Emit event)

🔷 Props: 부모 컴포넌트 → 자식 컴포넌트

  • 데이터를 전달하는데 사용되는 속성

🔷 One-Way Data Flow: 모든 props는 자식 속성과 부모 속성 사이에 하향식 단방향 바인딩을 형성 (one-way-down binding)

🔷 Props 특징

  • 부모 속성이 업데이트 되면 자식으로 흐르지만 그 반대는 안됨
  • 즉, 자식 컴포넌트 내부에서 props를 변경하려고 시도해서는 안되며 불가능
  • 또한 부모 컴포넌트가 업데이트 될 때마다 자식 컴포넌트의 모든 props가 최신 값으로 업데이트 됨
  • 부모 컴포넌트에서만 변경하고 이를 내려 받는 자식 컴포넌트는 자연스럽게 갱신

🤔 왜 단방향이어야 하는가?

  • 하위 컴포넌트가 실수로 상위 컴포넌트의 상태를 변경하여 앱에서의 데이터 흐름을 이해하기
    어렵게 만드는 것을 방지하기 위함이다.

🔷 App → Parent → ParentChild 컴포넌트 관계 작성

<template>
  <div>
    <h1>최상위 컴포넌트</h1>
    <Parent />
  </div>
</template>

<script setup>
import Parent from './components/Parent.vue';

</script>

<style scoped>

</style>
<template>
    <div>
        <h2>부모 컴포넌트</h2>
        <ParentChild />
    </div>
</template>

<script setup>
import ParentChild from './ParentChild.vue';

</script>

<style scoped>

</style>
<template>
    <div>
        <h3>자식 컴포넌트</h3>
    </div>
</template>

<script setup>

</script>

<style scoped>

</style>

🔷 Props 작성

  • 부모 컴포넌트 Parent에서 자식 컴포넌트 ParentChild에 보낼 props 작성
<!-- Parent.vue -->
<template>
  <div>
  	<ParentChild my-msg="message" />
  </div>
</template>

🔷 Props 선언

  • 부모 컴포넌트에서 보낸 props를 사용하기 위해서는 자식 컴포넌트에서 명시적인 props 선언이 필요

1. 문자열 배열을 사용한 선언

  • defineProps()를 사용하여 props를 선언
<!-- ParentChild.vue -->
<script setup>
	defineProps(['myMsg'])
</script> 

2. 객체를 사용한 선언

  • 객체 선언 문법의 각 객체 속성의 키는 props의 이름이 되며, 객체 속성의 값은 값이 될 데이터의 타입에 해당하는 생성자 함수 (Number, String, …) 여야 함

객체 선언 문법 사용을 권장한다.

  • prop에 타입을 지정하는 것은 컴포넌트를 가독성이 좋게 문서화 하는데 도움이 되며, 다른 개발자가 잘못된 유형을 전달할 때에 브라우저 콘솔에서 경고를 출력하도록 한다.
  • 추가로 prop에 대한 유효성 검사로써 활용 가능하다.
<!-- ParentChild.vue -->
<script setup>
  defineProps({
  	// 여러 타입 허용
    propB: [String, Number],
    // 문자열 필수
    propC: {
      type: String,
      required: true
    },
    // 기본 값을 가지는 숫자형
    propD: {
      type: Number,
      default: 10
    },
  })
</script>

🔷 prop 데이터 사용

<template>
    <div>
        <h3>자식 컴포넌트</h3>
        <p>{{myMsg}}</p>
    </div>
</template>

<script setup>
  //문자열 배열 형태로 props를 받아서 사용 가능
  //defineProps(['myMsg', ]);

  //defineProps와 객체를 사용한 선언 (확장이 용이 하기에 권장)
  const props = defineProps({
      myMsg: String,
  })

  console.log(props);
  console.log(props.myMsg);
</script>

<style scoped>

</style>
<template>
    <div>
        <h2>부모 컴포넌트</h2>
        <input type="text" v-model="name"/>
        <!-- my-msg에 담아 정적으로 할당 -->
        <ParentChild my-msg="message123" />
    </div>
</template>

<script setup>
import ParentChild from './ParentChild.vue';

</script>

<style scoped>

</style>

🔷 Props 세부사항

1. Props Name Casing (Props 이름 컨벤션)

▪ 선언 및 템플릿 참조 시 → camelCase

<p>{{ myMsg }}</p>
defineProps({
	myMsg: String
})

▪ 자식 컴포넌트로 전달 시 → kebab-case

<ParentChild my-msg="message" />

❗ 기술적으로 camelCase도 가능하나 HTML 속성 표기법과 동일하게 kebab-case로 표기할 것을 권장한다.

2. Static props & Dynamic props

  • 지금까지 작성한 것은 Static(정적) props
  • v-bind를 사용하여 동적으로 할당된 props를 사용할 수 있음

1) Dynamic props 정의

2) Dynamic props 선언 및 출력

3) Dynamic props 출력 확인

🔷 한 단계 더 내려보내면서 동적 할당쓰기

<template>
    <div>
        <h4>손자 컴포넌트</h4>
        <p>{{myMsg}}</p>
    </div>
</template>

<script setup>
defineProps({
    myMsg: String
})
</script>

<style scoped>

</style>
<template>
    <div>
        <h3>자식 컴포넌트</h3>
        <p>{{myMsg}}</p>
        <p>{{dynamicProps}}</p>
        <!-- v-bind를 활용하여 props를 동적으로 할당 -->
        <ParentGrandChild :my-msg="myMsg" />
    </div>
</template>

<script setup>
import ParentGrandChild from '@/components/ParentGrandChild.vue'
//문자열 배열 형태로 props를 받아서 사용 가능
//defineProps(['myMsg', ]);

//defineProps와 객체를 사용한 선언 (확장이 용이 하기에 권장)
const props = defineProps({
    myMsg: String,
    dynamicProps: String,
})

console.log(props);
console.log(props.myMsg);
</script>

<style scoped>

</style>
<template>
    <div>
        <h2>부모 컴포넌트</h2>
        <input type="text" v-model="name"/>
        <!-- my-msg에 담아 정적으로 할당 및 v-bind를 활용하여 props를 동적으로 할당 -->
        <ParentChild my-msg="message123" :dynamic-props="name"/>
    </div>
</template>

<script setup>
import { ref } from 'vue';
import ParentChild from './ParentChild.vue';

const name = ref('Alice');
</script>

<style scoped>

</style>

부모 -> 자식은 정적 할당 및 동적 할당, 자식 -> 손자는 동적 할당을 사용하였다.

정적 & 동적 props 주의 사항
정적 props는 문자열로써의 “1”을 전달한다면 동적 props는 숫자로써의 1을 전달한다.


📌 Component Events

Pass Props -> Emit event 이후
부모가 prop 데이터를 변경하도록 소리쳐야 한다!

🔷 $emit()

  • 자식 컴포넌트가 이벤트를 발생시켜 부모 컴포넌트로 데이터를 전달하는 역할의 메서드
  • $ 표기는 Vue 인스턴스나 컴포넌트 내에서 제공되는 전역 속성이나 메서드를 식별하기 위한 접두어

🔷 emit 메서드 구조

  • $emit(event, ...args)

  • event → 커스텀 이벤트 이름

  • args → 추가 인자

🔷 이벤트 발신 및 수신 (Emitting and Listening to Events)

  • $emit 을 사용하여 템플릿 표현식에서 직접 사용자 정의 이벤트를 발신
<button @click="$emit('someEvent')">클릭</button>
  • 그러면 부모는 v-on을 사용하여 수신할 수 있음
<ParentComp @some-event="someCallback" />

🔷 이벤트 발신 및 수신 과정

ParentChild에서 someEvent 라는 이름의 사용자 정의 이벤트를 발신
-> ParentChild의 부모 Parent는 v-on 을 사용하여 발신된 이벤트를 수신
-> 수신 후 처리할 로직 및 콜백함수 호출

💡 defineEmits()를 사용하여 명시적으로 발신할 이벤트를 선언할 수 있다.

🔷 이벤트 선언 방식으로 추가 버튼 작성 및 결과 확인

<template>
    <div>
        <h3>자식 컴포넌트</h3>
        <p>{{myMsg}}</p>
        <p>{{dynamicProps}}</p>
        <!-- emit으로 someEvent 전달 -->
        <button @click="$emit('someEvent')">클릭</button>
        <button @click="buttonClick">클릭2</button>
        <ParentGrandChild :my-msg="myMsg" />
    </div>
</template>

<script setup>
import ParentGrandChild from '@/components/ParentGrandChild.vue'
//문자열 배열 형태로 props를 받아서 사용 가능
//defineProps(['myMsg', ]);

//defineProps와 객체를 사용한 선언 (확장이 용이 하기에 권장)
const props = defineProps({
    myMsg: String,
    dynamicProps: String,
})

//명시적으로 emit 이벤트를 정의
const emit = defineEmits(['someEvent']);

const buttonClick = () => {
    emit("someEvent");
}

console.log(props);
console.log(props.myMsg);
</script>

<style scoped>

</style>
<template>
    <div>
        <h2>부모 컴포넌트</h2>
        <input type="text" v-model="name"/>
        <!-- my-msg에 담아 정적으로 할당 및 v-bind를 활용하여 props를 동적으로 할당 -->
        <ParentChild @some-event="someCallBack" my-msg="message123" :dynamic-props="name"/>
    </div>
</template>

<script setup>
import { ref } from 'vue';
import ParentChild from './ParentChild.vue';

const name = ref('Alice');

const someCallBack = () => {
    console.log('자식 컴포넌트의 이벤트를 수신')
}
</script>

<style scoped>

</style>

이벤트 발신 및 수신이 잘 되는 모습

🔷 이벤트 인자 (Event Arguments)

  • 이벤트 발신 시 추가 인자를 전달하여 값을 제공할 수 있다.
<template>
    <div>
        <h3>자식 컴포넌트</h3>
        <p>{{myMsg}}</p>
        <p>{{dynamicProps}}</p>
        <!-- v-bind를 활용하여 props를 동적으로 할당 -->
        <button @click="$emit('someEvent')">클릭</button>
        <button @click="buttonClick">클릭2</button>
        <button @click="emitArgsFunc">클릭3</button>
        <ParentGrandChild :my-msg="myMsg" />
    </div>
</template>

<script setup>
import ParentGrandChild from '@/components/ParentGrandChild.vue'
//문자열 배열 형태로 props를 받아서 사용 가능
//defineProps(['myMsg', ]);

//defineProps와 객체를 사용한 선언 (확장이 용이 하기에 권장)
const props = defineProps({
    myMsg: String,
    dynamicProps: String,
})

//명시적으로 emit 이벤트를 정의
const emit = defineEmits(['someEvent', 'emitArgs']);

const buttonClick = () => {
    emit("someEvent");
}

const emitArgsFunc = () => {
    emit("emitArgs", 1, 2, 3);
}

console.log(props);
console.log(props.myMsg);
</script>

<style scoped>

</style>
<template>
    <div>
        <h2>부모 컴포넌트</h2>
        <input type="text" v-model="name"/>
        <!-- my-msg에 담아 정적으로 할당 및 v-bind를 활용하여 props를 동적으로 할당 -->
        <ParentChild @emit-args="getNumber" @some-event="someCallBack" my-msg="message123" :dynamic-props="name"/>
    </div>
</template>

<script setup>
import { ref } from 'vue';
import ParentChild from './ParentChild.vue';

const name = ref('Alice');

const someCallBack = () => {
    console.log('자식 컴포넌트의 이벤트를 수신')
}

const getNumber = (...args) => {
    console.log(args)
}
</script>

<style scoped>

</style>

누를 때 마다 이벤트와 함께 인자가 부모에게 전달된다.

💡 emit 이벤트도 객체 선언 문법으로 작성 가능하다.
emit('submit', { email, password })

🔷 emit 이벤트 실습

  • 최하단 컴포넌트 ParentGrandChild에서 Parent 컴포넌트의 name 변수 변경 요청하기

1. ParentGrendChild 에서 이름 변경을 요청하는 이벤트 발신

<input type="text" v-model="newName">
<button @click="updateNameFunc">click!</button>
const emit = defineEmits(['updateName']);
const newName = ref('');
const updateNameFunc = () => {
    emit('updateName', newName.value);
}

2. 이벤트 수신 후 이름 변경을 요청하는 이벤트 발신 (ParentChild.vue)

 <ParentGrandChild @update-name="updateNameFunc" :my-msg="myMsg" />
const emit = defineEmits(['updateName']);
const updateNameFunc = (newName) => {
    emit('updateName', newName);
}

3. 이벤트 수신 후 이름 변수 변경 메서드 호출, 해당 변수를 prop으로 받는 모든 곳에서 자동 업데이트

<ParentChild @update-name="updateNameFunc" />
const updateNameFunc = (newName) => {
    name.value = newName;
}

손자 컴포넌트에서 input에 text를 담고 click 이벤트를 보내면 부모에서 받아 prop의 값을 바꿔 아래에 다시 전달한다.


prop drilling을 리액트를 배울 때 이해하기 어려웠는데, 이번에 확실히 알고 간다. vue에서의 prop drilling 해결을 어서 익히고 싶다는 마음 뿐이다.

profile
Hodie mihi, Cras tibi

0개의 댓글

관련 채용 정보