2년 전, 신입으로 인터뷰를 진행하면서 받은 질문 중 하나가 고차 함수였습니다.
제대로 답하지 못하자 일급 객체에 대해 알고 있는지 여쭤보셨고,
이에 대해서도 답변을 못했던 기억이 있습니다.
그로부터 2년이 지난 지금, 고차 함수 개념을 이용해 타이머 기능을 구현하며
다시 한번 고차 함수가 가능한 이유와 일급 객체가 무엇인지 복습할 수 있었습니다.
또한, custom hook과 HOC를 두고 어떤 방식으로 구현해야 할지
고민한 이야기도 작성해보고자 합니다.
일급 객체와 고차함수와의 연관성
일급 객체는 함수가 변수에 할당될 수 있고,
함수의 인자로 전달되거나 return 될 수 있는 특성을 의미하는데요.
고차 함수는 함수를 인자로 받거나 함수를 반환하는 함수를 말합니다.
React에서 컴포넌트는 함수 형태로 작성이 가능하잖아요?
그렇다면 컴포넌트는 곧 함수고, 하나의 컴포넌트를 인자로 받아
새로운 컴포넌트를 반환하는 함수처럼 사용한다면
고차 컴포넌트가 된다는 결론에 도달할 수 있죠.
Custom Hook VS HOC
지극히 주관적인 결론은
렌더링을 통한 결과물에 영향을 미치는 로직이라면 HOC,
로직의 분리만이 목표라면 Custom Hook을 사용하는 것이 좋았습니다.
Custom Hook은 HOC에 비해
props를 통한 값을 전달하는 방식이 덜 직관적으로 다가올 수 있고,
특정 상황에서는 HOC보다 코드가 더 복잡할 수 있습니다.
하지만 HOC도 중첩해 사용한다면 로직이 복잡해지고
가독성 또한 떨어집니다.
결론은 상황에 맞게 둘 중 하나를 선택해 사용하는 것이 좋다는 것입니다.
HOC를 사용한 타이머 기능 구현
// WithAutoClose.tsx
// 고차함수로서의 역할을 수행하는 컴포넌트입니다.
'use client';
import { useState, useEffect, ComponentType } from 'react';
import AutoCloseModal from '@/components/common/AutoCloseModal';
import { useDisclosure } from '@chakra-ui/react';
const WithAutoClose = <P extends object>(
WrappedComponent: ComponentType<P>,
initialTime: number,
) => {
const WithAutoCloseComponent = (props: P) => {
const [timeLeft, setTimeLeft] = useState<number>(initialTime);
const { isOpen, onOpen, onClose } = useDisclosure();
const [modalShown, setModalShown] = useState<boolean>(false);
useEffect(() => {
const timer = setInterval(() => {
setTimeLeft((prevTime) => {
if (prevTime <= 1000) {
if (!modalShown) {
setModalShown(true);
onOpen();
}
clearInterval(timer);
return 0;
}
return prevTime - 1000;
});
}, 1000);
return () => clearInterval(timer);
}, [timeLeft, onOpen, modalShown]);
return (
<>
<WrappedComponent {...props} timeLeft={timeLeft} />
{timeLeft === 0 && (
<AutoCloseModal
isOpen={isOpen}
onClose={onClose}
setTimeLeft={setTimeLeft}
setModalShown={setModalShown}
/>
)}
</>
);
};
WithAutoCloseComponent.displayName = `WithAutoClose(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`;
return WithAutoCloseComponent;
};
export default WithAutoClose;
// AutoCloseModal.tsx
// 고차함수에서의 카운트가 종료되면 발생하는 모달입니다.
// 모달의 카운트가 0이 된다면 useEffect의 else문이 동작합니다.
'use client';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import useSajuStore from '@/app/store/sajuUserInfo';
import useTarotStore from '@/app/store/tarotUserInfo';
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalFooter,
ModalBody,
Button,
Text,
} from '@chakra-ui/react';
import textStyles from '@/configs/textStyles';
interface modalType {
isOpen: boolean;
onClose: () => void;
setTimeLeft: (time: number) => void;
setModalShown: (show: boolean) => void;
}
const AutoCloseModal = ({
isOpen,
onClose,
setTimeLeft,
setModalShown,
}: modalType) => {
const router = useRouter();
const [modalTimeLeft, setModalTimeLeft] = useState<number>(10);
const { resetSajuState } = useSajuStore();
const { resetTarotState } = useTarotStore();
const handleClick = () => {
setTimeLeft(10000);
setModalTimeLeft(10);
setModalShown(false);
onClose();
};
useEffect(() => {
let modalTimer: NodeJS.Timeout;
if (modalTimeLeft > 0) {
modalTimer = setInterval(() => {
setModalTimeLeft((prevTime) => prevTime - 1);
}, 1000);
} else {
resetSajuState();
resetTarotState();
router.push('/');
setModalTimeLeft(0);
}
return () => clearInterval(modalTimer);
}, [modalTimeLeft, router]);
return (
<Modal onClose={onClose} isOpen={isOpen} isCentered>
<ModalOverlay />
<ModalContent maxW="660px" maxH="374px" p="60px">
<ModalHeader
p="0"
mb="20px"
style={textStyles.Bold_32}
textAlign="center"
>
자동 종료 안내
</ModalHeader>
<ModalBody h="72px" p="0" mb="60px">
<Text style={textStyles.Regular_24} textAlign="center">
<Text as="span" style={textStyles.Regular_24} color="#E80000">
{modalTimeLeft}초
</Text>
후에 처음 화면으로 돌아갑니다.
<br />
서비스를 계속 이용하시려면
<br />
머무르기 버튼을 눌러주세요.
</Text>
</ModalBody>
<ModalFooter p="0" justifyContent="center" gap="20px">
<Button
w="260px"
h="54px"
onClick={handleClick}
bg="#FFF"
borderRadius="60px"
border="2px solid #680BFF"
>
머무르기
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
};
export default AutoCloseModal;
사용 방법
(위의 코드를 사용한다는 전제)
// 시간은 ms 기준입니다. 10초를 원하신다면 1000* 10과 같이 처리해주셔야 합니다.
export default WithAutoClose(타이머 사용을 원하는 컴포넌트, 원하는 시간);
'Design Pattern' 카테고리의 다른 글
한 층 더 파본 singleton pattern (1) | 2024.11.27 |
---|---|
Compound pattern을 적용해 공통 모달을 만들어보자 (0) | 2024.11.15 |
완전한 싱글톤 패턴으로 리팩토링을 진행해보자 (0) | 2024.05.25 |
그저 작성했는데 싱글톤 패턴이었던 것에 대하여 (0) | 2024.05.23 |