이전 포스트에서 Long Press Menu를 구현해보았다. 그런데,, 실제로 써먹으려고 하니까 좀 많이 손을 봐야해서 아예 다시 리팩토링을 하려고 한다.
PressableContextMenu.tsx
/** React */ import { useState } from 'react'; /** Hook */ import { usePressableContextMenu } from './usePressableContextMenu'; /** Type */ import type { PointerEvent } from 'react'; import type { PressableContextMenuProp } from './PressableContextMenu.types'; export const PressableContextMenu = ({ onSelect, triggerButton, contextMenu, }: PressableContextMenuProp) => { const { buttonRef, startLongMenu, moveLongMenu, cleanUpLongMenu } = usePressableContextMenu(); const [isPointerDown, setIsPointerDown] = useState(false); const [hoverIndex, setHoverIndex] = useState(0); const handlePointerDown = (event: PointerEvent<HTMLDivElement\>) => { startLongMenu(event); setIsPointerDown(true); }; const handlePointerMove = (event: PointerEvent<HTMLDivElement\>) => { moveLongMenu(event, { onHover: setHoverIndex }); }; const handlePointerUp = () => { cleanUpLongMenu(); setIsPointerDown(false); onSelect(hoverIndex); }; return ( <div style={{ position: 'relative' }}> <div ref={buttonRef} onPointerDown={handlePointerDown} onPointerMove={handlePointerMove} onPointerUp={handlePointerUp} > {triggerButton(isPointerDown)} </div> {contextMenu(isPointerDown, hoverIndex)} </div> ); };
아주 심플해진 ContextMenu. ContextMenu에 필요한 것만 남기고 보니 간단해졌다. 해당 컴포넌트의 역할은 이렇다.
PressableContextMenu의 역할
- 버튼 Press 상태일 때 contextMenu를 띄운다.
- 버튼의 자식과 ContextMenu의 자식에게 필요한 상태를 넘겨준다.
조금 포인트 되는 부분은 Prop으로 받은 트리거 버튼과 컨텍스트 메뉴를 렌더하는 부분이겠다.
함수 렌더
{triggerButton(isPointerDown)} {contextMenu(isPointerDown, hoverIndex)}
일반적인 경우엔 {triggerButton}
으로 렌더하겠지만~ 따져보니 트리거 버튼에게 현재 눌렸는지 어쨌는지 정도 알려줘야 그에 대한 UI feedback을 해줄 것 같아 버튼 눌림에 대한 상태를 넘겨주었다. 컨텍스트 메뉴 역시 필요한 정보를 넘겨주었다.
이렇게 되면 상위 컴포넌트에선 이렇게 불러주면 된다.
상위 컴포넌트에서 Prop 넘겨주기
const returnTriggerButton = (isPressed: boolean) => { return ( <S.Button> // ... </S.Button> ); }; const returnContextMenu = (isOpened: boolean, hoverIndex: number) => { return ( <S.Ul> // ... </S.Ul> ); }; return ( <PressableContextMenu onSelect={handleSelect} triggerButton={returnTriggerButton} contextMenu={returnContextMenu} /> );
이런 느낌이다. 결국 이렇게 되면 triggerButton이나 contextMenu는 받은 상태들로 어떻게 나타낼지만 신경쓰면 된다.
CustomHook으로도 만들어서 해볼까 싶었는데~ 어떻게 구현하든 둘 다 비슷할 것 같긴 했다.
생각해보면 나는 비즈니스 로직과 UI를 같이 짜는 경향이 있다. 애니메이션도 뭔가 '동작'하는 거라 생각이 돼서 같이 넣곤 하는데,,, 앞으로는 좀 더 분리하도록 노력해야겠다.