관리자 페이지 프로젝트 3 - 지역 추가

철판김치덮밥·2022년 12월 20일
0

해당 프로젝트에서 “지역”의 자료형이 상당히 난감했음.

바로 아래와 같은 자료형을 가지고 있다.

{
	"경기도": {
		"안산시" : {
			"단원구" : {
				"고잔동" : "고잔동"
				"중앙동" : "중앙동"
			},
			"상록구" : {
				"사동" : "사동"
			}
		}	
	}
}

원하는 값들이 key로 들어가 있는 경우는 처음이라 당황했음.

그리고 이 자료형에서

  1. 각 레벨 삭제 기능 (동만 삭제, 구 삭제, 시 삭제)
  2. 각 레벨 보여주기
  3. 동 단위의 추가

이런 기능을 구현해 구현해야했음.

결과물:

UI:

<Grid container rowSpacing={1} columnSpacing={1}>
		//위 자료형을 string[]로 파싱
        {parseRegion(regions).map((region) => {
          return (
						//12를 기준으로 3을 먹겠다.
            <Grid key={region} item xs={3}>
              <Button
                variant="outlined"
                endIcon={<DeleteIcon />}
                onClick={() => {
                  handleDeleteRegion(region);
                }}
              >
                {region}
              </Button>
            </Grid>
          );
        })}
      </Grid>

MUI의 Grid를 이용하여 한줄에 4개씩 표기하도록 하였음,.

파싱 함수

dfs알고리즘을 사용해서 제작함.

여기서 만난 다양한 TypeError가 많이 성장시켜주었다.

//app/utils/parseRegion.ts

function parseRegion(region: Object | undefined, regionArray: string[] = [], level: number = 0, regionString = '') {
  if (region === null || region === undefined) return [];
									//마지막 띄어쓰기 지우기
  if (level !== 0) regionArray.push(regionString.slice(0, -1));
	//지역은 동레벨 까지만 존재하기 떄문에 도,시,구,동,[4]에 도착했을 때는 반환해준다.
  if (level === 4) {
    return [];
  }
  const keys = Object.keys(region);
  for (let i = 0; i < keys.length; i += 1) {
    const key = keys[i];
    parseRegion(region[key as keyof typeof region], regionArray, level + 1, `${regionString}${key} `);
  }
  return regionArray;
}

export default parseRegion;

만나는 모든 key들을 regionArray에 넣어주고 결과적으론 regionArray를 반환한다.

여기서 regionArray는 참조로 재귀함수 안으로 전달된다.

parseRegion(region[key as keyof typeof region])

위와 같이 자료형을 선언해준 이유는 바로 typescript 때문이다.

  1. string과 string 리터럴은 다르다.
  2. string리터럴만 object의 key 값으로 사용할 수 있다.
  3. string을 key로 사용하려 하면 typescript 에러가 발생한다.
  4. string을 region의 key로 사용할 수 있도록 타입 선언을 함을 통해 지역의 세부 레벨로 내려갈 수 있다.

삭제

//app/components/bannerAndRegionPage/regionBoard.tsx

const handleDeleteRegion = async (deleteRegion: string) => {
    const regionArray = deleteRegion.split(' ').map((e) => {
      return e as keyof typeof regions;
    });
    const newRegion = regions || {};
    switch (regionArray.length) {
      case 1:
        delete newRegion[regionArray[0]];
        break;
      case 2:
        delete newRegion[regionArray[0]][regionArray[1]];
        break;
      case 3:
        delete newRegion[regionArray[0]][regionArray[1]][regionArray[2]];
        break;
      case 4:
        delete newRegion[regionArray[0]][regionArray[1]][regionArray[2]][regionArray[3]];
        break;
      default:
        return;
    }
	//updateRegion은 region queryKey를 초기화 시켜준다.
    await updateRegion({ region: newRegion });
  };

지울 때는

  1. 해당 지역이름을 공백을 기준으로 나누고 모든 값을 region에서 key로 사용할 수 있도록 타입 선언을 해준다.
  2. 기존 지역의 복사본을 만든다.
  3. 지역이름의 길이에 따라 삭제할 레벨을 정하고 복사본에서 해당 지역을 삭제한다.
  4. 지역을 복사본으로 update한다.

지역 추가

//app/components/bannerAndRegionPage/addRegionField.tsx

import { Box, Button, TextField } from '@mui/material';
import deepmerge from 'deepmerge';
import React, { FormEvent, useState } from 'react';
import useUpdateCommon from '../../hooks/useUpdateCommon';

function AddRegionField({ regions }: { regions: Object | undefined }) {
  const [city, setCity] = useState('');
  const [town, setTown] = useState('');
  const [village, setVillage] = useState('');

  const { updateRegion } = useUpdateCommon();

  const addRegion = async (e: FormEvent) => {
    e.preventDefault();
    let addedRegion = {};
    village.split(' ').forEach((newVillage) => {
      addedRegion = deepmerge(addedRegion, {
        경기도: {
          [city]: {
            [town]: {
              [newVillage]: newVillage,
            },
          },
        },
      });
    });
    const newRegion = regions ? deepmerge(regions, addedRegion) : addedRegion;
    await updateRegion({ region: newRegion });
    setCity('');
    setTown('');
    setVillage('');
  };

  return (
    <form onSubmit={(e) => addRegion(e)}>
      <Box display="flex" justifyContent="space-between">
        <div className="flex gap-3">
          <TextField
           // 시 입력
          />
          <TextField
	       // 구 입력
          />
          <TextField
           // 동 입력
          />
        </div>
        <Button>
          서비스 지역 추가하기
        </Button>
      </Box>
    </form>
  );
}

export default AddRegionField;

여기서 주목해야할 부분은 deepMerge인데

기존에 존재하는 지역에 겹치는 건 빼고 안 겹치는 건 넣어주기 위해선 단순히 object를 더하는 것으로 해결이 되지 않고 깊은 비교를 통해 합치기 위해 deepMerge를 사용했다.

deepMerge에 대한 더 자세한 내용은 링크를 통해 잘 정리해준 분이 있어 참고하면 좋을 것 같다.

💡 기능 명세서 상으로 동을 띄어쓰기로 연결했을 때 모든 동을 추가할 수 있어야 하기 때문에 `village.split(' ').forEach`를 사용했다.

0개의 댓글