공통 select 박스 만들기 (feat.typescript , mui/material)

이명진·2023년 8월 22일
0

디자인할때 자주 사용되는 select 박스를 공통 컴포넌트로 제작하기로 마음먹었다.

주로 데이터를 받을때 객체 형식으로 받을때도 있고 아니면 그냥 단순하게 number| string 배열일 경우도 있어서 타입을 여럭가지 주려고 했는데 오류가 많이 터져서 간단하게작업할줄 알았던 공통 컴포넌트 제작을 반나절 가까이 소요해서 만들수 있었다.

다양한 에러들이 많이 나왔는데.. 에러를 수정해도 계속 에러가 나와서 하나하나씩 다 정리하지 못하였다. 에러가 나오면 정리하는 습관을 가져야 할텐데 에러가 나오면 바로바로 문제 해결하려고 몰두 하게 되어서 까먹게 되었다.

결과 적으로 도출된 select 공통 컴포넌트 를 제작할수 있었다.

props로 타이틀 이름명을 받고 label명과 선택할수 있는 값을 받는다.
list 는 option list 이다. valueType 은 리스트가 객체 형태일때 어떤 값을 value로 줄지 선택하기 위해서 넘겨 준다. selectNamekey도 value와 선택된 값의 이름이 다를 경우 key를 주기 위해서 받는다.

제네릭 타입을 건네주어서 쉽게 제작할수 있었는데 이게 참 어렵다.
제네릭 타입인지 아닌지. 얘가 제네릭 타입인데 이걸 어떻게 코드에게 설명해줘야 할지 잘 몰랐어서 오류가 많이 났었다.

다른 타입들 string | number | object 일 경우
typeof 를 사용해서 판별하면되는데 제네릭 타입일경우 어떻게 판별을 해야 할지 잘 몰라서 많이 헤맸다.

에러를 핸들링하기 쉽기 위해 타입스크르립트를 사용하는데.. 타입 에러를 해결하기 위해 시간이 엄청 소요되는 것을 보고 참 현타가 오기도 했다.

타입스크립트 그냥 만만하게 썼는데 이렇게 직접 코드를 사용해보니 타입스크립트가 만만하지 않다. 어렵다라고 느껴졌다.

특히나 valueType 은 keyof T 타입인데 자꾸 코드 상에서 T 내부에 valueType 을 못알아 듣는것이 많이 답답하였다.

“as T” 이런 구문은 안쓰려고 생각하고 있었는데 안쓰면 해결하기가 쉽지 않았다.

분명 T는 배열 타입인데 map 함수, filter 함수를 사용하면 에러가 나왔다.

아직도 기억나는 오류인데

공용 구조체 형식 '{ <S extends string>(predicate: (value: string, index: number, array: string[]) => value is S, thisArg?: any): S[]; (predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): string[]; } | { ...; } | { ...; }'의 각 멤버에 시그니처가 있지만, 해당 시그니처는 서로 호환되지 않습니다.

이러한 에러를 뿜어서 당황을 많이 했다. 호환되지 않아서 map과 filter함수를 사용하지 못해서 답답하기도 했다.

결과 적으로 완성한 코드이다.

import {
  FormControl,
  Grid,
  InputLabel,
  MenuItem,
  Select,
  SelectChangeEvent,
  Typography,
} from '@mui/material';
import { Dispatch, SetStateAction } from 'react';
type Props<T> = {
  selectName: string;
  label: string;
  value: T;
  list: T[];
  valueType?: keyof T;
  selectNameKey?: keyof T;
  onChange: Dispatch<SetStateAction<T>>;
};
export default function CustomSelect<T>({
  selectName,
  label,
  value,
  list,
  valueType,
  selectNameKey,
  onChange,
}: Props<T>) {
  const checkValueType = (item: T, key?: keyof T) => {
    if (
      item instanceof Object &&
      typeof item !== 'string' &&
      typeof item !== 'number'
    ) {
      if (key && key in item) {
        return item[key];
      }
    } else {
      return item;
    }
  };
  const handleChange = (e: SelectChangeEvent<T>) => {
    const { value } = e.target;

    if (valueType) {
      let target = list.filter(x => String(x[valueType]) === value)[0];

      onChange(target);
    } else {
      if (typeof value !== 'string') onChange(value);
    }
  };

  return (
    <Grid container alignItems={'center'} spacing={2} m="1rem">
      <Grid item>
        <Typography variant="h5">{selectName}</Typography>
      </Grid>
      <Grid item>
        <FormControl fullWidth>
          <InputLabel id={label}>{label}</InputLabel>
          <Select
            id={label}
            value={
              valueType &&
              value instanceof Object &&
              typeof value !== 'string' &&
              typeof value !== 'number'
                ? (checkValueType(value, valueType) as T)
                : value
            }
            label={label}
            onChange={handleChange}
          >
            {list.map(item => {
              return (
                <MenuItem
                  value={String(
                    checkValueType(item, valueType ? valueType : undefined)
                  )}
                >
                  {checkValueType(
                    item,
                    selectNameKey ? selectNameKey : undefined
                  )?.toString()}
                </MenuItem>
              );
            })}
          </Select>
        </FormControl>
      </Grid>
    </Grid>
  );
}

진짜 반나절 걸려서 머리가 터질듯이 오류를 해결하니 오류 정리고 뭐고 일단 해결했다 라는 생각에 만족했었다.

다음 코드 작성에 도움이 되는 해결책이 되었으면 좋겠다.

profile
프론트엔드 개발자 초보에서 고수까지!

0개의 댓글