본문 바로가기

개발/vue

Vue 3에서 컴포넌트 간 데이터 변동을 감지하고 관리하는 주요 방법들

반응형

Vue 3는 컴포넌트 간 통신을 위한 다양한 방법을 제공합니다. 오늘은 부모 컴포넌트와 자식 컴포넌트 사이에서 데이터 변동을 감지하는 주요 방법들에 대해 알아보겠습니다.

1. Props와 Emits

부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하고, 자식에서 부모로 이벤트를 전달합니다.

 

부모 컴포넌트:

<template>
  <ChildComponent :message="parentMessage" @update="handleUpdate" />
</template>

<script>
import { ref } from 'vue'

export default {
  setup() {
    const parentMessage = ref('Hello from parent')
    const handleUpdate = (newValue) => {
      console.log('Updated in parent:', newValue)
    }
    return { parentMessage, handleUpdate }
  }
}
</script>

 

자식 컴포넌트:

<template>
  <div>
    <p>{{ message }}</p>
    <button @click="sendUpdate">Send Update</button>
  </div>
</template>

<script>
export default {
  props: ['message'],
  emits: ['update'],
  setup(props, { emit }) {
    const sendUpdate = () => {
      emit('update', 'New message from child')
    }
    return { sendUpdate }
  }
}
</script>

 

2. v-model

컴포넌트에서 간단한 양방향 바인딩을 구현할 때 사용합니다.

 

부모 컴포넌트:

<template>
  <CustomInput v-model="inputValue" />
  <p>입력값: {{ inputValue }}</p>
</template>

<script>
import { ref } from 'vue'

export default {
  setup() {
    const inputValue = ref('')
    return { inputValue }
  }
}
</script>

 

자식 컴포넌트 (CustomInput):

<template>
  <input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />
</template>

<script>
export default {
  props: ['modelValue'],
  emits: ['update:modelValue']
}
</script>

 

3. provide/inject

깊은 컴포넌트 트리에서 데이터를 전달할 때 유용합니다.

 

최상위 컴포넌트:

<script>
import { provide, ref } from 'vue'

export default {
  setup() {
    const sharedValue = ref('Shared data')
    provide('sharedKey', sharedValue)
  }
}
</script>

 

깊이 중첩된 자식 컴포넌트:

<template>
  <p>{{ sharedValue }}</p>
</template>

<script>
import { inject } from 'vue'

export default {
  setup() {
    const sharedValue = inject('sharedKey')
    return { sharedValue }
  }
}
</script>

 

4. Vuex/Pinia (상태 관리)

큰 애플리케이션에서 전역 상태를 관리할 때 사용합니다. 여기서는 Pinia 예시를 들겠습니다.

 

스토어 정의:

import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  actions: {
    increment() {
      this.count++
    }
  }
})

 

컴포넌트에서 사용:

<template>
  <p>Count: {{ counter.count }}</p>
  <button @click="counter.increment">Increment</button>
</template>

<script>
import { useCounterStore } from './stores/counter'

export default {
  setup() {
    const counter = useCounterStore()
    return { counter }
  }
}
</script>

 

5. watchEffect와 watch

반응형 데이터의 변화를 감지하고 대응할 때 사용합니다.

<script>
import { ref, watchEffect, watch } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const doubleCount = ref(0)

    // watchEffect 예시
    watchEffect(() => {
      console.log('Count changed:', count.value)
    })

    // watch 예시
    watch(count, (newValue, oldValue) => {
      doubleCount.value = newValue * 2
    })

    const increment = () => {
      count.value++
    }

    return { count, doubleCount, increment }
  }
}
</script>

 

6. Composition API (ref, reactive, computed)

재사용 가능한 로직을 구성하고 복잡한 상태를 관리할 때 유용합니다.

<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Double Count: {{ doubleCount }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script>
import { ref, reactive, computed } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const state = reactive({
      name: 'John',
      age: 30
    })

    const doubleCount = computed(() => count.value * 2)

    const increment = () => {
      count.value++
      state.age++
    }

    return {
      count,
      state,
      doubleCount,
      increment
    }
  }
}
</script>

 

 

이러한 방법들을 프로젝트의 규모와 복잡성에 따라 적절히 선택하거나 조합하여 사용할 수 있습니다.

  • 작은 프로젝트: Props/Emits와 v-model만으로도 충분할 수 있습니다.
  • 중간 규모 프로젝트: Composition API를 활용하여 로직을 재사용하고, provide/inject로 깊은 컴포넌트 트리에서 데이터를 전달할 수 있습니다.
  • 큰 프로젝트: Pinia나 Vuex 같은 상태 관리 라이브러리를 사용하여 전역 상태를 효과적으로 관리할 수 있습니다.

 

각 방법의 장단점:

  1. Props/Emits: 간단하지만 깊은 컴포넌트 트리에서는 불편할 수 있습니다.
  2. v-model: 양방향 바인딩에 편리하지만 복잡한 로직에는 적합하지 않습니다.
  3. provide/inject: 깊은 컴포넌트 트리에 유용하지만 남용하면 코드 추적이 어려울 수 있습니다.
  4. Vuex/Pinia: 전역 상태 관리에 강력하지만 작은 프로젝트에서는 과도할 수 있습니다.
  5. watchEffect와 watch: 데이터 변화에 반응하기 좋지만, 과도한 사용은 성능 저하를 일으킬 수 있습니다.
  6. Composition API: 로직 재사용과 코드 구조화에 뛰어나지만, 학습 곡선이 있을 수 있습니다.

실제 프로젝트에서는 이러한 방법들을 상황에 맞게 조합하여 사용하는 것이 일반적입니다. 예를 들어:

<!-- ParentComponent.vue -->
<template>
  <div>
    <h1>{{ title }}</h1>
    <p>Total Count: {{ totalCount }}</p>
    <ChildComponent
      :initialCount="count"
      @increment="handleIncrement"
    />
  </div>
</template>

<script>
import { ref, computed, provide } from 'vue'
import { useCounterStore } from './stores/counter'
import ChildComponent from './ChildComponent.vue'

export default {
  components: { ChildComponent },
  setup() {
    const title = ref('Vue 3 Data Management Example')
    const count = ref(0)
    const counterStore = useCounterStore()

    const totalCount = computed(() => count.value + counterStore.count)

    provide('appTitle', title)

    const handleIncrement = (amount) => {
      count.value += amount
      counterStore.increment()
    }

    return { title, count, totalCount, handleIncrement }
  }
}
</script>

<!-- ChildComponent.vue -->
<template>
  <div>
    <h2>{{ appTitle }}</h2>
    <p>Local Count: {{ localCount }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script>
import { ref, inject, watch } from 'vue'

export default {
  props: ['initialCount'],
  emits: ['increment'],
  setup(props, { emit }) {
    const appTitle = inject('appTitle')
    const localCount = ref(props.initialCount)

    watch(localCount, (newValue) => {
      console.log('Local count changed:', newValue)
    })

    const increment = () => {
      localCount.value++
      emit('increment', 1)
    }

    return { appTitle, localCount, increment }
  }
}
</script>

 

이 예제에서는:

  1. Props와 Emits를 사용하여 부모-자식 컴포넌트 간 통신
  2. provide/inject로 앱 제목을 깊은 컴포넌트에 전달
  3. ref와 computed를 사용한 반응형 데이터 관리
  4. watch를 사용하여 로컬 카운트 변화 감지
  5. Pinia 스토어(useCounterStore)를 사용한 전역 상태 관리

이렇게 다양한 방법을 조합하여 사용하면, 각 상황에 가장 적합한 데이터 관리 전략을 구현할 수 있습니다. 중요한 것은 코드의 가독성, 유지보수성, 그리고 성능을 균형있게 고려하는 것입니다. 프로젝트의 규모가 커지면 컴포넌트를 더 작은 단위로 분리하고, 재사용 가능한 컴포지션 함수를 만들어 로직을 모듈화하는 것도 좋은 방법입니다.

반응형

'개발 > vue' 카테고리의 다른 글

countdown timer 설정  (0) 2024.08.20
Vue 3.0에서 타입스크립트 사용하기  (0) 2023.10.11
[Vue] Component 재사용  (0) 2023.01.17