2023. 12. 18. 18:45ㆍReact JS
useEffect(() => {
$(`#${id}`).hide();
}, []);
const fadeIn = () => {
$(`#${id}`).show(() => {
$(`#${id}`).fadeIn();
});
};
const fadeOut = () => {
$(`#${id}`).fadeOut(() => {
setMessages?.(defaultList);
});
};
이렇게 되어있는 컴포넌트가 있다.
아주 잘 동작하는 구문이지만 한가지 걸리는게 있었다.
현재 npm에서 jquery 라이브러리를 설치해서 사용하고 있는데 이 프로젝트 내에서 jquery라이브러리를 이 컴포넌트, 즉 한 군데에서만 사용하고 있다.
구글 크롬이 제공하는 기능이 lighthouse를 이용하여 treemap을 보니 한 군데에서만 쓰이면서 전체 자원의 2%나 사용하고 있다. 공간의 낭비라고 생각이 되어 이를 순수 자바스크립트 코드로 변경하기로 했다.
사용되는 jquery 함수는 세개가 나온다.
1. hide()
2. show()
3. fadeIn()
4. fadeOut()
살짝씩 뜯어보니 hide()는 display: none으로, show()는 display: '' 혹은 'block'으로 하고 있었다.
문제는 바로 fadeIn()과 fadeOut().
보이다시피 이건 애니메이션이 필요하다.
몇가지 고려사항이 있었다.
1. 이거 하나를 위해서 다른 css 파일을 만들고 싶지는 않다. 그리고 styled-component를 이참에 사용해보고 싶었기에 css 요소는 styled-component를 사용하였다.
2. 클래스 이름에 따라서 opacity, display 등등 css 속성을 다르게 주고 싶었다. 리액트는 선언형 프로그래밍 스타일을 추구하므로 state를 이용하여 .fade-in, .fade-out 이 두 가지 클래스를 입혔다.
3. board를 닫을 때에 messages를 빈배열([])로 만들어주려 하는데 opacity가 완전히 0이 될 때까지 기다려야 한다. 그리고 display는 none이 되어서는 안된다. jquery의 fadeIn/Out의 기본 duration은 400ms 라고 한다. 이를 이용하여 애니메이션이 완전히 종료되고 난 후에 messages를 빈배열로, display를 none으로 변경할 것이다.
4. 더 좋은 방법이 있겠지만 초반에 .hide()하는 방법은 마땅한것을 찾지 못했고 또다른 하나의 state를 만듦으로서 해당 컴포넌트의 연속적인 렌더링을 발생시키고 싶지는 않아서 useRef를 사용하였다. 더 좋은 방법이 있으면 업데이트로 하도록 하겠다.
하여 나온 결과물은 아래와 같다.
duration을 0.4로 상수화하였으며 css의 @keyframes 속성을 사용하여 중복된 코드를 줄이고 애니메이션을 만들었다.
초기 렌더링 시 useRef를 이용하여 board의 display를 none으로 하고 fadeIn() 함수 실행 시 board의 display를 block으로 설정하였다. 그리고 fadeOut()함수를 실행 한 뒤 지정된 duration * 1000 뒤에 messages를 []로 변경하였고 board의 display를 다시 none으로 변경하였다.
그리고 boolean 타입의 state를 이용하여 class 이름을 변경하여 각 class에 맞는 애니메션을 입혔다.
/**
* AlertBoardLayout 프로퍼티즈
*/
interface AlertBoardLayoutProps {
id?: string;
messages: AlertBoardMessagesProps[];
setMessages?: (e: AlertBoardMessagesProps[]) => void;
extraClass?: string;
isNotCloseable?: boolean;
withoutColDiv?: boolean;
}
const FADE_INOUT_DURATION = 0.4;
const BoardWrapper = styled.div`
.fade-in {
opacity: 1;
transition: opacity ${FADE_INOUT_DURATION}s;
-moz-animation: fadein ${FADE_INOUT_DURATION}s linear;
-webkit-animation: fadein ${FADE_INOUT_DURATION}s linear;
animation: fadein ${FADE_INOUT_DURATION}s linear;
}
.fade-out {
opacity: 0;
-moz-animation: fadeout ${FADE_INOUT_DURATION}s linear;
-webkit-animation: fadeout ${FADE_INOUT_DURATION}s linear;
animation: fadeout ${FADE_INOUT_DURATION}s linear;
}
@-webkit-keyframes fadein {
0% {
opacity: 0;
transform: scale(0);
transform-origin: top left;
}
100% {
opacity: 1;
transform: scale(1);
transform-origin: top left;
}
}
@keyframes fadein {
0% {
opacity: 0;
transform: scale(0);
transform-origin: top left;
}
100% {
opacity: 1;
transform: scale(1);
transform-origin: top left;
}
}
@-webkit-keyframes fadeout {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes fadeout {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
`;
/**
* TODO:
* 1. display: none, block 처리 제대로
* 2. useRef보다 더 나은, 기존의 자원을 최대한 활용하며 렌더링을 더 이상 유발하지 않는 방법 찾기
*/
const Board: React.FC<AlertBoardLayoutProps> = ({ id, extraClass, isNotCloseable, messages, setMessages, withoutColDiv }) => {
const [isBoardShow, setIsBoardShow] = useState<boolean>(false);
const boardRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (boardRef.current) boardRef.current.style.display = 'none';
}, []);
const fadeIn = useCallback(() => {
setIsBoardShow(true);
if (boardRef.current) boardRef.current.style.display = 'block';
}, []);
const fadeOut = useCallback(() => {
setIsBoardShow(false);
setTimeout(() => {
setMessages?.(defaultList);
if (boardRef.current) boardRef.current.style.display = 'none';
}, FADE_INOUT_DURATION * 1000);
}, [setMessages]);
useEffect(() => {
messages && messages.length > 0 ? fadeIn() : fadeOut();
}, [messages]);
return (
<BoardWrapper>
<div id={id} ref={boardRef} className={`${isBoardShow ? 'fade-in' : 'fade-out'} alert alert-warning alert-dismissible ${extraClass ?? ''} ${withoutColDiv ? '' : 'pdr0'}`} role="alert">
{isNotCloseable || (
<button type="button" className="close" aria-label="Close" onClick={fadeOut}>
<span aria-hidden="true">
<i className="fal fa-times-circle"></i>
</span>
</button>
)}
<ul className="list-unstyled mgb0">
{messages.map((e, index) => {
return (
<li key={`${e.labelName}${index}`} className={!withoutColDiv && e.labelColor ? e.labelColor : `${e.liClassName ? e.liClassName : ''}`}>
{e.labelIconName && <i className={`fal ${e.labelIconName}`}></i>}
<strong>{e.labelName ?? ' '}</strong>
{e.value ?? ' '}
</li>
);
})}
</ul>
</div>
</BoardWrapper>
);
};
Jquery fadeIn/Out 함수를 리액트 내에서 순수 자바스크립트로 변경함으로서 평소 제대로 실습해보고 싶었던 styled 컴포넌트를 사용해보았던 것이 큰 유익이라 할 수 있겠다. 결과물에는 styled-component에 props를 사용한 것이 없지만 messages && messages.length > 0 값에 따라서 display를 제어해보려 했고, 이를 BoardWrapper에 props로 넣어 사용해보기도 했다.(비록 실패했지만,,,) 이를 통해 styled-component에 대한 이해도를 더욱 높일 수 있었다.
뿐만 아니라 나름 css 공부를 하기도 해서 좋았고! 한군데에서만 쓰이고 있는 jquery 모듈을 걷어낸다는 것이 뭔가 후련한 기분이다.
'React JS' 카테고리의 다른 글
[React JS] webpack으로 build 시 html에 환경변수 넣는 법 (1) | 2024.01.26 |
---|---|
[React JS] Spring Boot 내에서 React 어플리케이션 구동시키기 (2) | 2024.01.25 |
[React JS] 무한히 늘어나는 숫자를 ###,###,### 형식으로 포매팅하기 (0) | 2023.06.23 |
[React JS] 1,000,000 형식의 가격에서 ,를 지웠을 때 그 앞 숫자도 지워지게 하는 법 (0) | 2023.03.30 |
[React JS]Sheet JS와 xlsx-populate 사용하여 스타일 된 XLSX 파일 다운 받기(1) (0) | 2023.02.06 |