개발 일지

[12월 5주차] Vue.js 3 상위 컴포넌트 ↔ 하위 컴포넌트 데이터 전달 방법

계란💕 2023. 12. 31. 01:25

 

얼마 전부터 회사에서 IoT 관련 웹 페이지를 만들고 있습니다.
그림과 같이 최상위 컴포넌트(Page) 안에 검색 컴포넌트(SearchForm)와 목록 컴포넌트(List)가 있고 그 안에 또 자식 컴포넌트(Modify)가 있는 형태입니다.
검색폼에 조건을 넣어서 조회하던 도중, 디바이스 정보를 등록하거나 수정한 다음, (검색 조건을 그대로 적용한) 새로운 목록이 조회되는 기능을 만들려고합니다. 그러기 위해서는 검색 정보(searchInfo)를 Page로 전달하고 또 다시 하위로 전달하는 구조가 필요합니다.
Vue.js 3에서 컴포넌트 간에 데이터를 주고 받는 방법을 정리하며 살펴보겠습니다. 

 

 

 

하위 컴포넌트(DeviceSearchForm) → 상위 컴포넌트(Page) 데이터 보내기

DeviceSearchForm 에서 defineExpose()를 이용하면 Page(상위 컴포넌트)로 데이터를 전달할 수 있습니다.
ref 속성은 Vue 인스턴스에 대한 참조를 만드는 역할을 합니다.
<hide/>
<template>
    <el-form 
        ref="ruleFormRef"
        :model="searchInfo"
    ..검색 관련 필드 
    />
</template>

<script setup>
const ruleFormRef = ref(null);

const searchInfo = reactive({     // 검색 정보
    fromDate: "",             
    toDate: "",
    paging: true
});

defineExpose({
   ruleFormRef: ruleFormRef
});

</script>

 

 

 

하위 컴포넌트(DeviceSearchForm) → 상위 컴포넌트(Page) 데이터 받기
상위 컴포넌트(Page) → 하위 컴포넌트(DeviceList) 데이터 보내기

앞서 검색폼에서 보낸 데이터를 Page 컴포넌트에서 받을 차례입니다.
<DeviceSearchForm ref="receivedData" .. />      const receivedData = ref(null);
위와 같이 선언하면 receivedData.value를 통해 수신한 데이터에 접근할 수 있습니다. 
그 다음 Page 의 다른 하위 컴포넌트인 List, Edit에 해당 정보를 전달하려합니다. 
DeviceSearchForm 으로부터 검색 정보를 받아서 watch()로 실시간 감시하면서 state.searchInfo에 저장하면 다음과 같이 DeviceList와 DeviceEdit 에 데이터를 보낼 수 있습니다. 
handleSearch(검색 메서드) 를 검색하기 위한 코드가 있습니다. 하위 컴포넌트에서는 emits("searchDevice") 를 통해서 상위 컴포넌트의 handleSearch() 를 사용할 수 있습니다. 
<hide/>
<template>
    <DeviceSearchForm 
        ref="receivedData"
        @searchDevice="handleSearch"
    />
    <DeviceList
        :searchInfo="state.searchInfo"
        @searchDevice="handleSearch"
    />
    <el-button type="primary" @click="dialogVisibleEdit = true">등록</el-button>
    <el-dialog v-model="dialogVisibleEdit" title="디바이스 등록">
        <DeviceEdit 
            :searchInfo="state.searchInfo"
            @cancelDialog="cancelDialog"
            @searchDevice="handleSearch"
        />
    </el-dialog>
</template>

<script setup>
const receivedData  = ref(null);	

const state = reactive({
  searchInfo: {}  // DeviceSearchForm으로부터 받아서 다시 Edit, List에 전달할 데이터
});

watch(() => receivedData.value, // SearchForm 으로부터 받은 데이터
  (crr) => {
    state.searchInfo = crr.ruleFormRef;
  }
)

const handleSearch = (data) =>{
    // 검색 메서드
}
</script>

 

 

 

상위 컴포넌트(Page) → 하위 컴포넌트(DeviceList) 데이터 받기

상위 컴포넌트( DeviceList ) → 하위 컴포넌트(DeviceModify) 데이터 보내기

앞서 검색폼에서 보낸 데이터를 Page에서 받았고 Page 가 보낸 데이터를 다시 List 컴포넌트에서 받을 차례입니다.
defineProps(상위 컴포넌트에서 보낸 데이터 정의)를 이용하면 위에서 보낸 데이터명을 받을 수 있습니다. 
그러면 메서드는 어떻게 받을까요?
defineEmits(상위 컴포넌트에서 보낸 메서드 정의) 안에 searchDevice를 정의하고, 검색하고 싶은 부분에서 emits("searchDevice", data)로 이벤트를 발생시킵니다. 그러면 상위에서 선언한 검색 메서드(handleSearch(data))를 사용할 수 있습니다. 
List 컴포넌트에는 각 디바이스에 대한 수정 팝업을 띄울 수 있도록 "수정" 버튼이 있는데 이 버튼을 클릭했을 때 나오는 수정 팝업 코드는 아래와 같습니다.
modifyDevice() 메서드 안을 보면 정상적으로 수정이 된 다음, emits()으로 디바이스 목록을 재조회할 수 있습니다.
<hide/>
<template>
	...디바이스 목록
  <el-dialog title="디바이스 수정" v-model="dialogVisibleModify" class="data">
      <DeviceModify
        :searchInfo="searchInfo"
        @cancelDialog="cancelDialog"
        @modifyDevice="modifyDevice"
        @searchDevice="searchDevice"
      />
    </el-dialog>
</template>

const emits = defineEmits(["searchDevice"]);

const props = defineProps({
  searchInfo:
    type: Object,
    required: true,
  }
})
const searchDevice = (data) => {
  emits("searchDevice", data);
};

const modifyDevice = () => {
    $axios
      .post($apiUrls.iot.device.modify, deviceInfo,  {
        headers: { "Content-Type": "application/json" },
      })
      .then((res) => {
        alert("수정되었습니다.");
        // TODO path 담기
        emits("searchDevice", {
          curPage: 1,
          searchInfo: props.searchInfo,
        })
        cancelDialog();
      })
      .catch((err) => {
        console.log(err);
        alert("수정에 실패하였습니다");
      });
    });
};

 

 


새로 알게 된 내용

상위 컴포넌트에서 하위 컴포넌트로 메서드, 데이터를 보내는 것은 익숙하게 해봤으나 반대로 데이터를 보내는 방법은 이번에 자세히 알게되었습니다. 
그리고 하위 컴포넌트에서 정의한 메서드를 상위 컴포넌트에서 호출하는 것은 권장되지 않는 다는 걸 새로 알게 되었습니다. Vue 의 기본 설계 원칙 중 하나인 '단방향 데이터 흐름(One-Way Data Flow)'에 어긋나기 때문입니다. 

 


 Vue의 기본 설계 원칙 

  • 단방향 데이터 흐름(One-Way Data Flow): 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달하면 하위 컴포넌트에서는 이벤트를 발생시켜서 상위 컴포넌트에 결과를 전달하는 방식을 말한다. 
  • 단일 파일 컴포넌트: 각 컴포넌트를 하나의 파일로 구성할 수 있다. 
  • 컴포넌트 기반: 컴포넌트 기반 아키텍처를 채택한다. 
  • 양방향 데이터 기반(Two-Way Data Binding): Vue는  입력 요소와 Vue 데이터 속성 간에 양방향 동기화를 제공한다. 
    • 입력 요소: <template> 안에 있는 요소 중 사용자가 입력할 수 있는 input 박스, 라디오 버튼, 체크박스 같은 것
    • Vue 데이터: Vue가 관리하고 있는 정보(<script> 안에 정의된 변수)를 말한다. 
  • 가상 DOM: Vue는 가상 DOM을 사용해서 성능을 최적화한다. 가상 DOM은 실제 DOM과 동기화되며, 변경된 부분만 실제 DOM에 적용해서 효율적으로 렌더링한다. 
  • Render 함수: 템플릿 대신에 JavaScript를 사용해서 가상 DOM을 생성하는 방법을 제공한다. 보통은 <template> 안에 UI를 작성하지만 렌더링 로직이 복잡하면 JavaScript를 사용하는 게 더 효율적인 경우가 있다. 

 


TITLE

  • CON
  • CON

'개발 일지' 카테고리의 다른 글

OkHttp vs RestTemplate  (0) 2023.03.18