사이드 프로젝트를 시작할 때 당장 정해진 디자인이 없다면 빠르게 시작할 수 있는 MUI와 같은 컴포넌트 라이브러리를 선택하는 것이 좋다. 실무에선 디자인 시스템이 있는 회사라면 딱히 고민 없이 FE 개발을 시작할 것이다.
위와 같은 경우가 아니라면 나와 같이 직접 컴포넌트를 처음부터 만들어야 할 것이다. 그런데 막상 '어떻게 시작하지?', '어떻게 재사용할 수 있는 컴포넌트를 만들지?'와 같은 고민 속에 빠져 쉽사리 코드를 치지 못하고 시간만 보낼 수도 있다. 우선 나는 만들고 고치고 만들고 고쳐보자란 마음으로 시작을 하였고 그 첫 기록을 남겨보려고 한다. 여러 가지 컴포넌트 중 SelectBox를 첫 번째로 만들어보았다.
1. SelectBox 기본 디자인 요소는 뭐가 있을까
우선 기본 디자인으로 아래와 같은 케이스들을 생각해 볼 수 있었다.
- label이 있거나 없거나
- placeholder가 있거나 없거나
- key와 value로 구성된 options
- validate의 error시 표시(ex. 메시지, 글씨 색상, selectbox 배경색 또는 테두리색)
- option을 선택했을 때 선택된 값, 선택된 표시(ex. 글씨 색상, selectbox 배경색 또는 테두리색)
- disabled
2. 마크업 부터 해볼까
<form>
<label for="age-select">Age</label>
<select id="age-select" name="age">
<option value="">나이</option>
<option value="10">10대</option>
<option value="20">20대</option>
<option value="30">30대</option>
<option value="40">40대</option>
</select>
<p>나이를 선택해주세요</p>
</form>
참고로 MUI Select 예시 코드를 보면 아래와 같이 구현이 되어 있다.
<FormControl variant="filled" className={classes.formControl}>
<InputLabel id="demo-simple-select-filled-label">Age</InputLabel>
<Select
labelId="demo-simple-select-filled-label"
id="demo-simple-select-filled"
value={age}
onChange={handleChange}
>
<MenuItem value="">
<em>None</em>
</MenuItem>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
</FormControl>
3. type 설정을 해보자
디자인과 마크업 구조를 보면, 필요한 값을 대략적으로 나열할 수 있게 되었다.
다만 typescript를 쓰면서 타입 정의에 대해 꼼꼼하게 미리 정하고, 맞춰서 쓰는 걸 습관화해야 한다는 걸 느끼고 있는 요즘이다.
우선 나는 label이나 placeholder는 있거나 없거나 조건을 두었기 때문에 optional로 두었고
options는 배열, selectedValue는 string, error는 메시지를 보여줄 것이므로 string, disabled는 true/false로, 마지막으로 handleChange 함수타입으로 정의해 두었다.
interface ISelectBoxProps {
label?: string
options: [];
selectedValue: string;
placeholder?: string;
error: string;
disabled: boolean;
handleChange: (e: ChangeEvent<HTMLSelectElement>) => void;
}
4. 컴포넌트를 만들어보자
프로젝트 구조에서 src> components > SelectBox.tsx 생성하였다. 그리고 tailwind css를 사용했다.
interface IOption {
key: number | string;
value: string;
}
interface ISelectBoxProps {
label?: string
options: [];
selectedValue: string;
placeholder?: string;
error: string;
disabled: boolean;
handleChange: (e: ChangeEvent<HTMLSelectElement>) => void;
}
export default function SelectBox({
label,
options,
selectedValue,
placeholder,
error,
disabled,
handleChange,
}: ISelectBoxProps) {
return (
<div className="relative">
{label && <label className="text-xs">{label}</label>}
<div>
<select
className="h-12 w-full rounded-lg bg-none px-3"
value={selectedValue}
onChange={(e) => handleChange(e)}
disabled={disabled}
>
<option value="" className="">
{placeholder}
</option>
{options.map((option) => (
<option
key={`select_${option.key}`}
value={option.key}
data-name={option.value}
>
{option.value}
</option>
))}
</select>
</div>
{error && (
<p className="absolute mt-0.5 text-xs text-[#F44336]">{error}</p>
)}
</div>
);
}
그럼 SelectBox 호출부에서는 아래와 같이 사용하면 된다.
import SelectBox from "./SelectBox";
import { useState } from "react";
export default function PageWrapper() {
const [selectValue, setSelectValue] = useState<IUniversity>("");
const handleSelectChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
setSelectValue(event.target.value)
};
return (
<form>
<SelectBox
label="Age"
options={[]}
selectedValue={selectValue}
placeholder="나이"
error="나이를 선택해주세요"
disabled={false}
handleChange={handleSelectChange}
></SelectBox>
</form>
);
}
마무리
SelectBox를 어떻게 만들고 관리할지 정리를 해보았다. 위 내용은 혼자 개발하는 입장이다 보니 모든 선택 기준이 작업자인 나의 마음대로 진행이 되었는데 실무에선 조금 더 깊게 고민을 해봐야 할 것이다.
예를 들어 호출하는 API에 따라 타입이 달라질 수 있기 때문에 BE와 API 논의할 때 타입을 잘 체크해 봐야 할 것 같고,
SelectBox를 여러개 관리하게 될 수 있고,
SelectBox의 선택값에 따라 조건이 달라지는 비즈니스 로직이 생길 수 있는 등 다양한 케이스들을 마주하게 될 것 같다.
'Develop' 카테고리의 다른 글
[React Native] 시작해보기 (0) | 2024.08.12 |
---|---|
'오늘 하루 그만 보기' 정보는 어디에 저장이 될까? (0) | 2024.07.22 |
[React] 나의 프로젝트에 맞는 state 상태관리는? - Context 편 (0) | 2024.05.31 |
[Tailwind CSS] React+Vite 환경에 설치, 그런데 적용이 안됨. 왜죠? (0) | 2024.05.22 |
[공공데이터] API 인증키 오류 _ 'SERVICE KEY IS NOT REGISTERED ERROR' (0) | 2024.05.11 |