프로젝트를 진행하면서 예약 시스템을 구현하는데 날짜와 시간을 고르는 기능이 필요했다. 라이브러리를 찾아보다가 가장 적합해보이는 react-datePicker 를 선택해 사용하게 됐다.
사용하다가 몇가지 문제가 있었는데,
첫번째는 디자인 커스텀의 한계가 있었다. 라이브러리다 보니 내가 원하는대로 디자인을 커스텀하는데 한계가 있었다.
두번째는 달력에 마우스를 올려다 놓거나 드래그만해도 날짜가 모두 리렌더링 되는 이슈가 있었다. 사실 성능상으로는 엄청나게 크리티컬한 문제는 아니라고 생각했지만, 그냥 넘어가자니 너무 찝찝했고 이 부분을 고쳐보려고 여러 방법을 동원해봤지만 해결이 되지 않았다😭
바퀴를 다시 발명하지 마라
-프로그래밍 격언-
"바퀴를 다시 발명하지 마라" 라는 유명한 격언이 있다. 이미 만들어져 있는 것을 굳이 다시 만들지 마라는 의미인데, 잘 만들어진 라이브러리를 사용하는 편이 업무 효율성도 높아지고 안정성이 높다는 편에서 개발자 입장에서도 굳이 다시 만들 필요성 느끼지는 못한다고 생각한다.
그러나 모든 케이스에서 라이브러리가 적합한 것은 아닌 것 같다. 특히 이번에 프로젝트를 하면서 라이브러리를 커스텀화 하는데 너무 많은 시간과 에너지를 쏟아야 했다.
내가 오픈 소스를 수정하거나 자유자재로 커스텀 할 수 있는 개발 능력이 부족해서 그럴 수도 있지만, 라이브러리를 사용자의 니즈에 맞게 딱 맞춰서 개발한다는 것은 굉장히 까다롭고 어려운 부분이다.
이러한 이유로 아쉬운대로 라이브러리를 계속 사용해야 하나 아니면 내가 직접 구현해볼까 하는 고민이 들었다.
잘 만들어진 라이브러리를 사용하는 편이 업무 효율성도 높아지고 앞으로 여러 기능이 추가된다고 해도 라이브러리는 이미 구현되어 있는 기능들이 많기 때문에 빨리 대응할 수도 있다는 장점이 있었다.
그러나 서비스의 디자인이 통일되지 않는 문제와 리렌더링 이슈 그리고 라이브러리를 사용하면서 아쉬웠던 부분들이 있었기에 직접 구현하면 이러한 점을 보완 할 수 있다는 장점이 있다.
고민끝에 결국 직접 구현해보기로 했다. 현업에서는 더 많은 요구사항이 있을텐데 그럴때마다 라이브러리에 의존할 수는 없을 것 같다는 생각이 컸고, 결국 개발자라면 직접 구현할 줄도 알아야 한다는 생각이 들었다.
react-datePicker git 에서 코드를 clone 해와서 코드를 하나씩 살펴봤다.
구조는 아래와 같다.
코드를 살펴보다 onMouseEnter 라는 함수가 눈에 띄었다. 마우스를 올리기만 해도 리렌더링이 발생하는 이슈였기 때문에 함수명을 봤을때 연관성이 있어 보였다.
따라서 이 함수를 따라가면서 살펴보니 Day 컴포넌트에서 onMouseEnter 이벤트 핸들러에 handleMouseEnter 라는 props 를 전달하여 마우스 이벤트가 발생할때마다 함수가 실행되는 구조임을 파악할 수 있었다.
의심되는 부분을 확인하기 위해 해당 함수를 주석처리 해보니 달력에 마우스를 올려놓아도 렌더링이 발생하지 않았다!!🤗
결국 onMouseEnter 이벤트 핸들러가 등록되어 있었고, 따라서 마우스 이벤트가 발생할때마다 등록된 함수가 실행되어 계속해서 렌더링이 발생했던 것이다.
export default class Day extends React.Component {
.... (생략)
render = () => (
<div
ref={this.dayEl}
className={this.getClassNames(this.props.day)}
onKeyDown={this.handleOnKeyDown}
onClick={this.handleClick}
// onMouseEnter={this.handleMouseEnter}
tabIndex={this.getTabIndex()}
aria-label={this.getAriaLabel()}
role="option"
title={this.getTitle()}
aria-disabled={this.isDisabled()}
aria-current={this.isCurrentDay() ? "date" : undefined}
aria-selected={this.isSelected() || this.isInRange()}
>
{this.renderDayContents()}
{this.getTitle() !== "" && (
<span className="overlay">{this.getTitle()}</span>
)}
</div>
);
원래는 달력에 mouseover 될때마다 렌더링이 발생한다.
그러나, onMouseEnter 함수를 주석 처리하면 달력 위에 마우스를 올려도 렌더링이 발생하지 않는다. 그러나 마우스가 달력에서 벗어나면 다시 렌더링이 발생한다.
마찬가지로 코드에서 onMouseLeave 함수를 찾아보니 Month 컴포넌트에서 이 함수를 호출하고 있었는데, 이 부분에 주석처리를 하니 아래와 같이 mouseLeave 에도 렌더링이 발생하지 않았다!!
렌더링을 발생시키는 원인을 추론해서 그 부분을 주석처리 했더니 리렌더링이 발생하지 않는 것까지 확인했다. 그렇다면 왜 이런 구조로 만들어졌는지에 대한 궁금증이 생긴다.
컴포넌트 구조를 보면 아래와 같은 양상을 띤다.
index.jsx -> Calendar.jsx -> Month.jsx -> Week.jsx -> Day.jsx
부모 컴포넌트에서 자식 컴포넌트로 함수를 props 로 주입하고 있다.
[index.jsx]
const WrappedCalendar = onClickOutside(Calendar)
...(생략)
renderCalendar = () => {
return (
<WrappedCalendar
onDayMouseEnter={this.props.onDayMouseEnter}
onMonthMouseLeave={this.props.onMonthMouseLeave}
onYearMouseEnter={this.props.onYearMouseEnter}
onYearMouseLeave={this.props.onYearMouseLeave}
...(생략)
/>
)
}
Day 컴포넌트에서는 onMouseEnter 이벤트 핸들러에 handleMouseEnter 함수를 설정했다. handleMouseEnter 함수 내부에서는 props로 전달 받은 onMouseEnter 함수를 호출하고 있다.
[Day.jsx]
...(생략)
handleMouseEnter = (event) => {
if (!this.isDisabled() && this.props.onMouseEnter) {
this.props.onMouseEnter(event);
}
};
render = () => (
<div
ref={this.dayEl}
className={this.getClassNames(this.props.day)}
onKeyDown={this.handleOnKeyDown}
onClick={this.handleClick}
onMouseEnter={this.handleMouseEnter}
...(생략)
</div>
);
disabled 처리된 12, 13일만 제외하고는 콘솔에 날짜가 렌더링 되어 나타나는 걸 볼 수 있다.
onMouseEnter 이벤트 핸들러는 마우스 이벤트가 발생했을때 날짜를 알려주는 용도로 사용하도록 만들어진 것으로 추론된다. 그러나 이것을 활용한 부분은 없었다. (찾지 못했을 수도 있지만)
해당 기능을 활성화한 이유에 대해 명확한 이유를 찾기는 어려웠지만, 아마도 이벤트 핸들러를 등록하여 마우스가 달력 위에 올라갈 때 특정 동작을 수행하고자 했을 것으로 추측된다.
아니면 나중에 기능을 확장하거나 추후에 필요할 수도 있는 유연성을 유지하기 위해 마우스 이벤트를 미리 구현해놓은 것일 수도 있다.
그러나 이 기능이 항상 필요한 것은 아니므로, 불필요한 렌더링을 방지하기 위해 props를 추가하여 필요할 때만 이 기능을 활성화할 수 있도록 구현하는 것이 더 낫지 않을까 하는 생각이 든다.
처음으로 라이브러리 코드를 분석(?) 해봤는데 생각보다 의도를 파악하는게 어려웠다. 그래도 내가 쉽게 가져다 사용하던 라이브러리를 이렇게 자세히 들여다보니 구조를 파악할 수 있어 공부가 많이 되었다.
직접 구현할 Calendar 에는 라이브러리 기반으로 좀 더 보안해서 만들어보면 좋을 것 같다.