PDF로 된 기획안을 바탕으로 React를 이용해 Admin 페이지를 만드는 과제를 받았다. 주어진 시간은 약 48시간! 솔직히 말하자면, 기한이 너무 빠듯해서 아름다운 CSS나 클린코드에 대한 욕심은 접어두고 일단 기능이 제대로 작동하도록 구현하는 것에 중점을 뒀다.
내가 맡은 파트는 크게 3가지였다.
그중 2번 form이 가장 복잡했다. 추가해야할 항목이 있을 경우 "항목 추가" 버튼을 통해 카테고리를 추가할 수 있고, "삭제" 버튼을 누르면 생성한 카테고리를 삭제할 수 있어야 했다. 뿐만 아니라 입력폼 자체를 사용자가 원하는 만큼 추가로 생성할 수도 있어야 했다. 이미지로 보여주면 쉬울 텐데 기업과제를 유출할 수 없으니 다음처럼 만들어봤다.
Input을 사용할 일이 워낙 많은 만큼 공용 컴포넌트로 먼저 만들었는데, 다른 팀원들도 손쉽게 가져다 쓸 수 있으려면 width나 height 같은 값을 원하는 대로 줄 수 있도록 해야 했다. 구현한 코드는 다음과 같다.
import React from "react";
import styled from "styled-components";
const Input = ({ width, height = "38",borderRadius = "5px", ...rest }) => {
return <StyledInput {...rest} width={width} height={height} />;
};
export default Input;
const StyledInput = styled.input`
width: ${({ width }) => width}px;
height: ${({ height }) => height}px;
border-radius: ${({ borderRadius }) => borderRadius};
border: 1px solid #d3d3d3;
padding-left: 10px;
`;
이번 과재의 경우 Input은 height과 border-radius 값이 아주 적은 예외를 제외하고 동일했다. 그래서 번번히 귀찮게 값을 내려주지 않아도 되도록 "height = 38"처럼 디폴트값을 주었다. 이번 프로젝트를 하며 배운 것은 rest parameter였다. {...rest}처럼 적어주면, placeholder={} value={} 이런 식으로 일일이 적어두지 않아도 된다. 우리말로 "나머지 매개변수"라는 이름처럼, 정해두지 않은 수의 매개변수를 배열로 받아주기 때문이다. 그래서 width나 border-radius같은 스타일 값만 적어주면 된다. 이렇게 편리할 수가.
Input을 다른 컴포넌트에서 가져다 사용할 때는 이렇게 쓰면 된다.
<Input
name="title"
placeholder="항목 제목 자유 입력"
onChange={optionForm.handleChange}
value={optionForm.values.title}
/>
name={name}이나 onChange 함수 같은 것을 적어두지 않았지만 모두 정상적으로 작동한다. rest 짱!
이번 form 과제를 하면서 html 공부의 필요성을 느꼈다. 그동안 나름대로 semantic tag도 사용하고 html을 잘 쓰고 있다고 생각했는데 역시... 프로그래미밍 세계엔 녹록한 게 없다. 이걸 느끼게 된 계기를 설명하자면, 버튼을 클릭하면 form에 새로운 카테고리가 추가되는 이벤트를 만들었는데 이상하게 클릭 직후 새 카테고리가 잠깐 보였다가 사라졌다. (여기까지만 읽고도 문제가 뭔지 맞춘 사람이 있을 것이다.) 처음에 내가 썼던 코드는 다음과 같다.
const onAddCategory = () => {
const { title, placeholder } = optionForm.values;
if (title === "" || placeholder === "") return;
const id = Math.random();
optionForm.clear();
setInputList([
...inputList,
{
id,
title,
category: title,
placeholder,
essential: false,
},
]);
};
<AddCategoryButton
width="100"
height="38"
variant="secondary"
onClick={onAddCategory}
>
항목 추가
</AddButton>
먼저 이 form은 여러 카테고리가 담겨 있는 배열을 map을 돌려 보여주는 방식이다. AddButton을 누르면 onAddCategory()라는 함수로 사용자가 입력한 값을 새로운 카테고리로 추가하는 식으로 코드를 작성했다. 그런데 아까 말했듯이 버튼을 누르면 새 항목이 아주 찰나 반짝이고 사라져버렸다. 대체 원인이 뭘까? 고민하며 머리를 쥐어뜯었는데 해결책이 너무 간단했다.
<AddCategoryButton
width="100"
height="38"
variant="secondary"
type="button"
onClick={onAddCategory}
>
바로 이처럼 버튼에 타입을 지정해주는 것이었다. form 태그 안에 들어있는 버튼은 기본적으로 type="submit" 속성을 가진다. 그래서 버튼을 눌렀을 때 내가 원하는 대로 동작하지 않은 것이다.
그래도 이 부분을 구현하면서 뿌듯했던 점이 있다면 커스텀 훅을 직접 만들어본 것이다. 아주 간단한 훅이지만, 처음 만들어본 거라 내게는 의미가 있다.
import { useState } from "react";
export default function useForm({ initialValues }) {
const [values, setValues] = useState(initialValues);
const handleChange = (event) => {
const { name, value } = event.target;
setValues({ ...values, [name]: value });
};
const clear = () => {
setValues(initialValues);
};
return {
values,
handleChange,
clear,
};
}
사용자가 input창에 입력한 값들을 각각 key와 value로 받아 저장하고, clear()를 통해 입력창을 다시 깨끗하게 하는 코드다.
아까 말했던 것처럼 일단 html의 form 관련 속성을 배웠다. 한 페이지 안에 2개의 form 태그를 쓸 수 없고, form 안에 들어있는 버튼은 자동적으로 type="submit"이 된다거나 하는 것들 말이다. 그외에도 이런 저런 이슈를 해결하며 배운 점이 정말 많다. 팀 프로젝트였던만큼 협업 과정에서 팀원들이 짜는 코드를 보며 그분들께 정말 많이 배울 수 있었다. (말이 나와서 말인데 팀이 정말 좋았다. 이런 말 매번 팀 작업한 후기에 쓰는 거 같은데 진짜로.. 아무래도 내가 인복은 정말 타고난 것 같다 😚 )
하지만 무엇보다 이번 프로젝트를 통해 얻은 수확은 React로 구현하는 프로젝트의 성능에 대해 생각해보게 된 점이다. React로 무언가를 구현할 때 잘게 컴포넌트를 나누는 것에 대해서 '그냥 그렇게 해야 하나보다' 하고 수동적으로 흡수했지 '왜' 그렇게 나누는지에 대해서 깊게 생각해본 적이 없었다. 그런데 팀원분께서 React developer tool을 깔고 개발자 도구에서 profiler를 실행시켜 렌더링 되는 컴포넌트가 하이라이트 되는 모습을 보여주셨는데, 머리를 맞은 듯한 기분이었다. 분리해두지 않으면 값이 하나가 바뀔 때마다 모든 게 리렌더링 되고, 이런 게 쌓여서 성능 저하가 생긴다는 것을 깨달았다.