아이콘 컴포넌트
divdivdiv의 메인 화면 아이콘은 총 세 가지 종류(폴더, 이미지, 포춘쿠키)로 구성되어 있으며, 각각의 아이콘은 서로 다른 정보와 기능을 갖고 있다. 그러나 일부 스타일을 공유하고 있고, 더블 클릭 시 동작을 수행하며, 사용자가 원하는 위치로 드래그할 수 있다는 공통적인 특성이 있다. 따라서 이 아이콘들을 우선 하나의 컴포넌트로 묶은 뒤, 아이콘 상태를 개별적으로 관리해야 할 필요가 생겼다. 컴포넌트 이름은 DraggableComponent()
로 지었다.
function DraggableComponent(props: {
className: string;
path: string;
type: string;
title: TitleProps;
width: number;
height: number;
}) {
const { className, path, type, title, width, height } = props;
const handleIconClick = (type: string) => {
// ...
};
const draggableContent = (
<div
className={`${styles["icon"]} ${styles[className]}`}
onDoubleClick={() => {
isMobile ? undefined : handleIconClick(type);
}}
onClick={() => {
isMobile ? handleIconClick(type) : undefined;
}}
>
<div
className={styles["icon-image"]}
style={{
// ...
}}
></div>
<div
className={styles["icon-title"]}
style={{
// ...
}}
>
<div>{title[language]}</div>
</div>
</div>
);
return isMobile ? (
<React.Fragment>{draggableContent}</React.Fragment>
) : (
<Draggable>{draggableContent}</Draggable>
);
}
모바일 화면이 아닌 경우에만 아이콘에 드래그 기능을 넣기 위해 최초 렌더링 시 현재 화면의 뷰포트 너비를 판별하는 변수를 만들었다. window.innerWidth
가 630 미만일 때는 모바일 화면으로 처리되어 드래그 기능 없이 아이콘을 터치(onClick
)하면 이벤트가 발생한다. 반대의 경우에는 아이콘에 드래그 기능이 추가되며, 더블클릭 했을 때(onDoubleClick
)에만 이벤트가 발생한다.
클릭 이벤트 함수
조건문을 통해 클릭 이벤트를 하나로 묶었다. 아이콘 타입에 따라 실행되는 함수가 달라진다.
const handleIconClick = (type: string) => {
if (type === "fortune") {
handleFortuneClick();
} else if (type === "folder") {
window.open(path, "_blank");
} else if (type === "image") {
handleImageClick(path);
}
};
아이콘 상태 관리
아이콘 타입별 이미지 크기는 서로 조금씩 다르기 때문에, 아이콘의 타입에 맞는 너비와 높이를 한꺼번에 관리할 수 있게 iconSize
라는 객체를 따로 만들어두었다. 아이콘 제목 또한 효과적인 상태 관리를 위해 별도의 라이브러리에 iconTitle
객체를 만들었다.
const iconSize = {
folder: {
width: 80,
height: 65,
},
image: {
width: 72,
height: 96,
},
fortune: {
width: 80,
height: 83,
},
};
// data.ts
export const iconTitle = {
blog: {
en: "Blog",
ko: "블로그",
},
music: {
en: "Carver Chart",
ko: "카버 차트",
},
// ...
};
메인 화면 리렌더링 이슈 처리
한편 이미지 타입 아이콘의 경우 더블 클릭하면 모달 창이 열리는데, 이때 한 가지 이슈가 발생했다. 모달창을 여닫을 때마다 사용자가 변경한 아이콘 위치가 초기화되었다. 이를 방지하기 위해 DraggableComponent
의 부모 컴포넌트에 해당하는 Icons
를 만들고, React
의 memo 기능을 활용해 props
의 값이 변경되지 않을 때는 리렌더링되지 않도록 처리했다.
function Icons({ setImgSrc, setImgAlt, setShowImage, language, isMobile }: IconsProps) {
// ...
function DraggableComponent(props: {
className: string;
path: string;
type: string;
title: TitleProps;
width: number;
height: number;
}) {
const { className, path, type, title, width, height } = props;
const handleIconClick = (type: string) => {
// ...
};
const draggableContent = (
// ...
);
return isMobile ? (
<React.Fragment>{draggableContent}</React.Fragment>
) : (
<Draggable>{draggableContent}</Draggable>
);
}
return (
// ...
);
}
const MemoizedIcons = memo(Icons);
컴포넌트 반환
부모 컴포넌트에서는 다음과 같은 형태로 반환해 각각의 아이콘을 화면에 표시했다.
export default function Main() {
// ...
return (
<React.Fragment>
<DraggableComponent
className="icon-blog"
path="https://blog.divdivdiv.com"
type="folder"
title={iconTitle.blog}
width={iconSize.folder.width}
height={iconSize.folder.height}
/>
<DraggableComponent
className="icon-music"
path="/music"
type="folder"
title={iconTitle.music}
width={iconSize.folder.width}
height={iconSize.folder.height}
/>
// ...
<DraggableComponent
className="icon-cat"
path="cat"
type="image"
title={iconTitle.cat}
width={iconSize.image.width}
height={iconSize.image.height}
/>
<DraggableComponent
className="icon-me"
path="me"
type="image"
title={iconTitle.me}
width={iconSize.image.width}
height={iconSize.image.height}
/>
<DraggableComponent
className="icon-fortune"
path="fortune"
type="fortune"
title={iconTitle.fortune}
width={iconSize.fortune.width}
height={iconSize.fortune.height}
/>
// ...
</React.Fragment>
);
}