이전 글에서는 재귀 호출을 통해 데이터 구조를 트리 형태로 변환하여 컴포넌트의 데이터로 활용했던 방법에 대해 설명드렸습니다.
재귀적 트리구조를 활용한 데이터 변환
사용자 지역 정보를 입력받는 Form을 개발하면서, 서버에서 전달하는 평면 데이터(flat data)를 Cascader 컴포넌트에서 요구하는 트리 구조로 변환해야 하는 문제가 있었습니다.예를 들어, 서버에서는
hin6150.tistory.com
사용자 지역 정보를 입력받는 폼(Form)을 개발하는 과정에서, 서버에서 전달되는 평면 데이터(flat data)를 Cascader 컴포넌트에서 요구하는 트리 구조로 변환해야 하는 상황이 있었습니다.
이번 글에서는 해당 컴포넌트를 개선해서, 특정 단체에 속한 지역만을 반환하는 기능을 구현하고자 합니다.
문제 상황
1. API 반환 데이터: 특정 단체에 속한 지역을 가져오는 API에서는 자식 노드만 반환합니다.
2. 컴포넌트 요구사항: Cascader 컴포넌트에서는 계층 구조를 표현하기 위해 부모 노드 정보도 필요합니다.
해결 방안
1. 자식 노드 기준 순회:
자식 노드만 있는 데이터와 전체 노드 데이터를 참조하여, 각 자식 노드의 parentId와 일치하는 노드를 전체 데이터에서 순회하며 수집합니다.
2. 재귀적 부모 수집:
1번 과정을 재귀 호출을 통해 최상위 부모 노드까지 반복하여 필요한 모든 부모 노드를 수집합니다.
3. 병합 후 옵션 구성:
최종적으로 수집한 노드들을 합쳐 Cascader의 options를 구성합니다.
재귀 호출을 통해 자식 노드들의 부모를 찾아 하나의 배열로 병합하는 방식으로 해결 방안을 구현하였습니다. 다만, 이 경우 전체 지역 데이터가 포함된 API와 특정 단체에 속한 지역 API를 두 번 호출해야 하는 문제가 발생합니다.
이를 해결하기 위해 TanStack의 Query에서 제공하는 Cache 기능을 활용할 수 있습니다.
컴포넌트 구조와 캐시 활용 이유
현재 컴포넌트 구조는 다음과 같이 구성되어 있습니다.
- Input Form 생성: 사용자 입력 폼을 생성합니다.
- 지역 선택 Input 숨김: 특정 단체의 입력이 없을 경우, 지역 선택 Input을 hidden 처리합니다.
- 단체 입력시 데이터 전달: 특정 단체의 입력이 들어오면, 해당 Input에 단체의 id 값을 포함하여 데이터를 전달합니다.
- 지역 표기: 전달된 데이터를 기반으로 사용자가 선택 가능한 지역을 표기합니다.
두 번째 과정에서는, 해당 Input이 숨김 처리되어 있더라도 컴포넌트가 DOM에 렌더링되는 과정에서 React-Query의 기본 GET 요청이 단체 id 값 없이 발생하게 됩니다. 이로 인해 전체 지역 데이터가 한 번 이상 렌더링되며, 이후 사용자가 단체 id 값을 입력하면 다시 한 번 요청이 발생하는 문제가 있습니다.
따라서 QueryKey가 빈 객체({})인 query에는 전체 데이터가 저장되어 있으며, 이후 두 데이터를 비교하여 상단의 해결 방안을 적용할 수 있습니다.
예시 코드
// 관련 함수들을 관리하는 클래스 내 addParents 함수
static addParents(
child: GetAreaResDto,
cachedAreas: GetAreaResDto[],
finalMap: Map<number, GetAreaResDto>,
depth: number = 0,
maxDepth: number = 3
): void {
if (depth >= maxDepth || !child.parentId) return;
const parent = cachedAreas.find(
(p) => p.id === child.parentId
);
if (parent) {
finalMap.set(parent.id, parent);
AreaClassification.addParents(
parent,
cachedAreas,
finalMap,
depth + 1,
maxDepth
);
}
}
// QueryKey를 통해 데이터를 관리합니다.
const { data } = useQuery({
queryKey: [RegionQuery.key, query],
queryFn: () => areaApi.areasControllerFindAll(query),
select: (res) => res.data
});
// 캐시된 전체 지역 데이터를 불러옵니다.
const regionsWithoutVendor = queryClient.getQueryData<{
data: { data: GetAreaResDto[] }
}>([RegionQuery.key, {}])?.data;
// 지역 데이터를 병합합니다.
const mergedAreas = useMemo(() => {
const finalMap = new Map<number, GetAreaResDto>();
data.data.forEach((area) =>
AreaClassification.addParents(
area,
regionsWithoutVendor.data,
finalMap
)
);
data.data.forEach((area) =>
finalMap.set(area.id, area)
);
return Array.from(finalMap.values());
}, [data, regionsWithoutVendor]);
추가 대안
만약 해당 API 요청에서 부모 지역 정보를 함께 전달한다면, API 호출이 불필요하게 두 번 발생하는 문제를 해결할 수 있습니다.
이 경우, useQuery의 enabled 옵션을 활용하여 조건이 만족될 때만 요청하도록 설정할 수 있습니다.
'개발' 카테고리의 다른 글
Ref를 이용해 전역 모달 시스템 구축하기 (0) | 2025.03.24 |
---|---|
재귀 호출을 통한 데이터 구조 변환 (0) | 2025.03.09 |