🛎 보러가기 🛒
🛎 들어가며
프로젝트에서 사용할 모양새로 변경하기 위해 다듬던 중, 정말 저렇게 스타일과 관련된 옵션을 바리바리 내려주는게 좋을까? 하는 의문이 들었다. 로직이 딱 필요한 거면 UI 없이 훅을 리턴하는 식으로 작성할 수도 있을 것이고, 혹은 코어 로직을 따로 분리하여 Headless UI로 독립적인 모듈로 관리하게 할 수도 있을 것 같았다.
로직이 담긴 코어 라이브러리는 추후에 분리하는 작업을 가지기로 하고,
일단은 디자인된 모양새로 사용할 수 있게 불필요해보이는 Props를 제거하고 + 같은 역할이지만 이리저리 분산된 코드들을 좀 더 정돈해보는 건 어떨까 생각했다.
🛎 주요 변경사항
🧹 1. 관심사 분리하기
셀렉터에서는 선택 모드가 day (요일 기준)냐, date (날짜 기준)냐에 따라서 하는 역할이 조금씩 다르다.
date 모드에서는 직관적으로 생각할 수 있듯이 마우스로 드래깅 되는 모든 셀들을 기록하고, 드래그 이벤트가 끝나면 기록된 셀들을 유저에게 전달되는 timeSlots 배열에 세팅하면된다.
그러나 day 모드에서는 일요일부터 ~ 토요일까지만 셀로 표현이 되어있다. 요일에 해당하는 셀들이 선택되면, 유저가 선택한 날짜 중에 같은 요일과 시간을 가진 모든 슬롯들을 찾아 이 것을 timeSlots 배열에 전달해줘야한다.
기존에는 대부분의 곳에서 mode 정보를 받아서, 모드에 따라 다른 행동을 할 수 있도록 했었다.
예시로 같은 슬롯인지를 비교하는 areTimeSlotsEqual 함수를 살펴보자.
export const areTimeSlotsEqual = (
slot1: TimeSlot,
slot2: TimeSlot,
mode: 'date' | 'day',
) => {
if (mode === 'day') {
return (
slot1.day === slot2.day &&
slot1.endTime === slot2.endTime &&
slot1.startTime === slot2.startTime
);
return (
slot1.date === slot2.date &&
slot1.endTime === slot2.endTime &&
slot1.startTime === slot2.startTime
);
};
day 모드 일때는 요일과 시간대만 비교하면 되지만, date 모드일 때는 date 정보와 시간을 비교해야한다.
이러니까 거의 모든 곳에서 mode 정보를 받아 분기를 나눠야했고, 비슷한데 조금 다른 로직만 두 번씩 중복해서 작성하는 느낌이 들었다.
또한 date 모드와 day 모드가 로직이 동일한 곳도 있고, 크게 다르게 생긴 곳도 있어서 작성할 때도 헷갈렸다.
셀렉터가 셀을 선택하는 로직과 사용자에게 전달할 데이터를 업데이트하는 로직을 모두 알고 있기 때문에 이런 문제가 발생한 것이라 생각해서 드래그 이벤트에 따른 슬롯 선택에 집중하는 부분과 사용자에게 전달할 데이터를 관리하는 부분을 나눠 각자의 역할에 집중할 수 있게 해야겠다고 생각했다.
구조는 다음과 같다.
function DraggableSelector(props) {
// 얻은 정보를 바탕으로 최종적으로 사용자에게 제공되는 시간 슬롯 배열을 세팅해 전달한다.
// ...
return (
<Selector {...props} />
)
}
function Selector(props) {
// date 모드를 기준으로 시간슬롯들을 선택한다.
// ...
return (
// ...
)
}
아래 빨간 네모 부분은 date 모드에서는 사용자가 선택한 날짜에 맞게 기준으로 세팅이 되는데,
(선택된 날짜대로 칸이 보이는 date 모드와는 다르게) day 모드에서는 처음부터 일주일에 해당하는 모든 셀들이 보여야한다.
날짜(date) 모드에서는 사용자가 셋한 dates (날짜들) 그대로 내려보내주고, 요일(day) 모드일 때는 일요일부터 ~ 토요일까지 7일에 해당하는 mock 데이터를 내려준다.
const datesForDayMode = useMemo(() => {
return [
new Date('2023-08-20'),
new Date('2023-08-21'),
new Date('2023-08-22'),
new Date('2023-08-23'),
new Date('2023-08-24'),
new Date('2023-08-25'),
new Date('2023-08-26'),
];
}, [mode]);
그렇게 Selector 컴포넌트에서 mockDates에 대한 드래그 이벤트를 끝나고 거기에 해당하는 셀들을 선택해서 바리바리 가져오면, DraggableSelector에서 이것을 보고 사용자가 실제로 설정한 날짜와 비교해 적합한 날짜와 시간에 해당하는 셀들을 timeSlots에 최종적으로 세팅해주게 된다.
if (mode === 'day') {
const updatedTimeSlots: TimeSlot[] = [];
selectedTimeSlots?.forEach(slot => {
// 유저가 실제로 가져온 날짜들 (dates)를 보고
// day(요일)과 startTime, endTime이 일치하는 (=== 동일한) 슬롯들을
// updatedTimeSlots에 푸시해준다.
});
setTimeSlots(updatedTimeSlots);
}
🧹 2. 깜빡임 이슈 해결하기
mode를 바꿀 때나, 날짜를 변경할 때, 선택된 timeSlots들이 초기화되어야하는 경우 렌더가 다시 일어나는데 그 때 깜빡임이 발생했다.
값이 업데이트 되지 않았는데 렌더 - 그 후 useEffect를 통한 값이 업데이트 되어 깜빡임 이슈가 발생했음을 알게 되었다.
🚨 useEffect vs useLayoutEffect
*useEffect
- 일부 state를 즉시 바꿔 렌더하지 않아도 될 때 사용
- 화면에 렌더링된 후에 비동기적으로 실행된다.
- 컴포넌트가 화면에 페인트 된 이후에 실행된다.
*useEffect의 라이프사이클
*useLayoutEffect
- 화면이 업데이트 되기 전에 동기적으로 실행된다.
- dom이 페인트 되기 직전에 실행된다.
*useLayoutEffect의 라이프사이클
일단 데이터 초기화를 하는 부분 중에 중복을 제거해줬고,
그 후 state가 먼저 적용되어야 하는 부분에 한해 useLayoutEffect으로 변경해 해결해주었다.
더이상 데이터가 초기화 되거나 변경되어도 깜빡이지 않는다.
🧹 3. 예시 페이지 업데이트
여러 컨트롤들을 추가해 playground 페이지를 업데이트했다.
react-draggable-selector
react-draggable-selector.vercel.app
🛎 마무리하며
데이터를 다루는 인터페이스는 달라지지 않았지만 스타일과 관련된 옵션들이 많이 빠지는 인터페이스 변화가 있어 버전 2.x.x로 업데이트해 배포했는데, 초기 설계의 중요성 + 추상화의 중요성에 대해 생각하는 계기가 되었다.
일단 한 번 배포하면 기존의 사용자까지 커버할 수 있도록 이후 변화들을 반영해야하는데 기반이 많이 불안정하면 그 뒤의 작업이 꽤나 고난일 것 같았다. 소프트웨어에서의 중요한 키워드는 역시 '변화'인 것 같단 생각이 강하게 든다.
늘 여러 명이 사용한다면? 동시에 사용하게 된다면? 을 염두에 두고 초기 구성을 하는 습관을 들여야지.
🛎 레퍼런스
* useEffect vs useLayoutEffect
'프로젝트 기록' 카테고리의 다른 글
[Meetable] Styled-components를 twin.macro로 마이그레이션하기 (0) | 2023.08.20 |
---|---|
[React-draggable-selector] 셀렉터 라이브러리 제작하기 (3) - npm 배포하기 (0) | 2023.08.16 |
[React-draggable-selector] 셀렉터 라이브러리 제작하기 (2) - 코드 톺아보기 (0) | 2023.08.16 |
[React-draggable-selector] 셀렉터 라이브러리 제작하기 (1) - 개요 및 기능탐색 (0) | 2023.08.12 |
[Meetable] Draggable-time-selector 구현기 (with REACT, TS) (4) | 2023.08.01 |