얼마 전부터 회사에서 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 |
---|