일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 계획
- s3 bucket
- 삶
- 신입 프론트엔드
- react
- 개인프로젝트
- 신입 이력서
- 캐플라이어
- 개인 프로젝트
- TypeScript
- 기획
- 회고
- aws s3
- MONGOOSE
- 신입
- 대학졸업
- 공부
- 신입 개발자
- 백엔드
- 이력서
- Javascript
- CSS
- 프론트엔드
- 개발
- 개발자 이력서
- 구상
- Next.js 13
- Next.js
- Today
- Total
개발 마라톤
9/22 - React의 요소 관리 - 재렌더링과 Virtual DOM 본문
BannerSection의 완성
오늘이 되서야 BannerSection을 완성시켰다.
단순히 애니메이션을 적용하고 이벤트를 적용하는데 생각보다 많은 버그, 오류가 있었다.
React 프로젝트는 이제 2번째이지만, 아직 배울 것이 많고, 그것이 또 흥미롭다고 생각한다.
있었던 이슈는 다음과 같다.
내부 컴포넌트의 애니메이션이 이벤트 시에 초기화 되는 현상
( 내부 컴포넌트가 상위 컴포넌트의 상태 변화 시 모든 정보가 초기화되는 현상 )
onMouseEnter 등의 이벤트를 통해 클래스를 토글할 때 애니메이션 초기화 되는 이슈 · Issue #19 · 1004ljy980/CharFlyer (github.com)
onMouseEnter 등의 이벤트를 통해 클래스를 토글할 때 애니메이션 초기화 되는 이슈 · Issue #19 · 1004l
export default function BannerList({ postList, isFlip = false, }: { postList: Array<TypeIntroductionPostList>; isFlip?: boolean; }) { // 마우스가 올라갔을 때 애니메이션을 멈추기 위한 상태 const [stop, setStop] = useStat...
github.com
이슈가 있었던 코드는 다음과 같았다.
export default function BannerList({
postList,
isFlip = false,
}: {
postList: Array<TypeIntroductionPostList>;
isFlip?: boolean;
}) {
// 마우스가 올라갔을 때 애니메이션을 멈추기 위한 상태
const [stop, setStop] = useState(false);
const onStop = () => setStop(true);
const onRun = () => setStop(false);
// 20개의 비행기로 리스트가 구성됩니다.
const airplaneList: Array<TypeIntroductionPostList> = [];
for (let i = 0, j = 0; i < 20; i++) {
j == postList.length && (j = 0);
airplaneList.push(postList[j++]);
}
enum AirplaneListTypes {
Original = 'ORIGIN',
Clone = 'CLONE',
}
// 원본, 복사본
const AirplaneList = ({ listType }: { listType: AirplaneListTypes }) => {
const listClasses = [
styles.airplanList,
listType == AirplaneListTypes.Original ? styles.original : styles.clone,
stop ? styles.stop : null,
].join(' ');
return (
<ul className={listClasses}>
{airplaneList.map((airplane, index) => {
return (
<li key={index} className={styles.airplane}>
<Image
className={styles.airplaneImage}
src="/image/paper_airplane.png"
fill={true}
style={{ objectFit: 'contain' }}
sizes="100%"
alt={`${airplane.title}`}
/>
<div
className={`${styles.thumbnail} ${isFlip ? styles.flip : ''}`}
>
<Image
src={`${airplane.thumbnail}`}
fill={true}
style={{ objectFit: 'cover' }}
sizes="100%"
alt={`${airplane.title}의 소개 사진`}
/>
</div>
</li>
);
})}
</ul>
);
};
return (
<div
className={`${styles.listWrap} ${isFlip ? styles.flip : ''}`}
onMouseEnter={onStop}
onMouseLeave={onRun}
>
<AirplaneList listType={AirplaneListTypes.Original} />
<AirplaneList listType={AirplaneListTypes.Clone} />
</div>
);
}
&.stop {
animation-play-state: paused;
}
위의 코드로 onMouse 이벤트를 통해 토클 클래스를 통해 애니메이션을 조정하고 싶었다.
그러나, 결과적으로는 이벤트 발생 시에 애니메이션은 멈췄으나, 애니메이션 정보 자체가 초기화되어 초기 상태로 돌아가는 문제가 있었다.
예상 원인으로는 React는 상태에 관련된 컴포넌트는 상태가 변경될 때, 아에 다시 그리기 때문에 애니메이션 정보가 초기화되는 것이라고 생각했다.
Render and Commit – React
The library for web and native user interfaces
react.dev
해당 내용에 관하여 관련된 React 공식 문서를 확인해 보았다.
공식 문서에는 그 컴포넌트(혹은 상위 컴포넌트)의 상태 변화 시에 재렌더링 한다고 명시한다.
또한 DOM 요소 변화에 대한 언급도 되어있는데 내용은 다음과 같다.
React는 렌더링 간에 차이가 있는 경우에만 DOM 노드를 변경합니다.
윗 내용에 대해 예시가 있다.
예를 들어, 매초 부모로부터 전달된 다른 props로 다시 렌더링하는 구성 요소가 있습니다.
<input>에 일부 텍스트를 추가하여 해당 값을 업데이트할 수 있지만 구성 요소가 다시 렌더링될 때 텍스트가 사라지지 않는 방법을 확인하세요.
윗 예시에서는 만약 Clock 컴포넌트가 time의 변화로 인해 완전히 전부 재랜더링 된다면,
input 의 내용이 계속 초기화 될 것임을 예시로 보여주고 있다.
하지만 그렇지 않는다는 것이, 변화하는 요소만 DOM 변경이 일어난다는 의미이다.
즉, 렌더링 간에 차이가 있다면 DOM 요소 자체를 바꾸게 된다.
이 내용이 지금 이슈 '내부 컴포넌트의 애니메이션이 이벤트 시에 초기화 되는 현상'과 연관된다고 생각했다.
내가 작성한 코드의 `AirplaneList`는 상위 컴포넌트인 `BannerList`의 하위 컴포넌트이다.
하위 컴포넌트는 상위 컴포넌트의 상태 변화가 일어나면 재렌더링 되기 때문에,
이는 렌더링 간에 차이가 존재하는 결과가 된다.
즉, DOM 요소 자체가 변화가 되며, 이는 새로운 요소 작성을 의미하고, 애니메이션이 초기화 되는 결과를 의미할 수 있다.
결론) 상위 컴포넌트의 상태가 변화할때, 내부 컴포넌트는 DOM 요소 자체가 새로 쓰이기 때문에 애니메이션이 초기화 될 것이다.
논의)
추가적으로,
그렇다면 내부 상태에 의해 클래스가 토글 될 때는 왜 애니메이션이 초기화되지 않을까에 대한 고민을 해보았다.
이 내용에는 React가 기존 JS가 사용하지 않는
Virtual DOM이라는 개념을 운용하고 있는 것에 대해 힌트를 얻을 수 있었다.
[React] DOM이란? Virtual DOM을 사용하는 이유? (velog.io)
Virtual DOM이란 React가 운용하는 DOM과 비슷한 개념으로,
일반 DOM과 다르게 HTML 생성의 모든 정보보단, 핵심적인 정보만 가지고 있는 DOM의 요약본과 같은 것이다.
React는 Virtual DOM을 내부에 자바스크립트 객체 형태로 다룸으로써,
더 가볍게 DOM의 상태를 확인 및 관리하고, 상태와 같은 변경사항이 있는 컴포넌트의 DOM만 비교 수정하여
실제의 DOM을 전부 변경하지 않고, 업데이트가 필요한 부분만 효율적으로 변경할 수 있도록 한다.
실제 DOM만을 다뤘다면, 위의 그림과 같이 변경이 필요한 DOM요소만 빨간색(변경점)이 되는 것이아니라,
모든 요소가 빨간색이 되었을 것이다.
결국에 Virtual DOM을 통해 React는 자원 관리의 시점에서도 효율적인 리렌더링을 이끌어 낼 수 있다.
이런 Virtual DOM의 개념으로,
상태가 변경된 컴포넌트는, 변경된 점을 포함해 CSS 애니메이션은 애니메이션 정보가 그대로 Virtual DOM에 저장된다.
이유는 React는 최소한의 변화만 이끌어 내기위해 Virtual DOM을 사용하고 있고, 애니메이션 또한 변하지 않았으면 하기 때문이다.
결론적으로 이런 Virtual DOM의 정보를 통해 DOM을 수정하기 때문에, 애니메이션 정보는 그대로 유지될 수 있다.
해결)
각 컴포넌트의 상태가 다른 컴포넌트의 렌더링에 영향을 주지 않도록, 컴포넌트 계층을 생각하며 작성한다.
즉, AirplaneList의 내부 컴포넌트는 상위 컴포넌트의 BannerList의 stop 상태에 따라 DOM요소가 재렌더링 되므로
AirplaneList의 내부의 요소인 특정 클래스의 애니메이션 정보까지 Virtual DOM에 저장된다고 확신할 수 없다.
그러므로 AirplaneList의 모든 정보를 BannerList에 직접 작성하여 문제를 해결하려 한다.
State: A Component's Memory – React
State: A Component's Memory – React
The library for web and native user interfaces
react.dev
해당 공식문서에 따르면, 상태 state는 공유되지 않고 비공개적인 개인의 것이라고 말한다.
그러므로 각 비행기(airplane)에 hover 이벤트를 판별하기 위해서는 공유되는 state를 사용하면 안되고,
각 비행기 요소로 구성되어야 하며, 각각의 state를 통해 이벤트를 판별해야 한다는 생각이 들었다.
최종 코드는 다음과 같다.
// BannerList 컴포넌트의 반환부분
const listClasses = [styles.airplaneList, stop ? styles.stop : null].join(
' '
);
return (
<div
className={`${styles.listWrap} ${isFlip ? styles.flip : ''}`}
onMouseEnter={onStop}
onMouseLeave={onRun}
>
{/* 원본리스트 */}
<ul className={`${listClasses} ${styles.original}`}>
{airplaneList.map((airplane, index) => {
const [hover, setHover] = useState(false);
return (
<li
key={`original : ${index}`}
className={styles.airplane}
onMouseEnter={() => {
onStop();
setHover(true);
}}
onMouseLeave={() => {
onRun();
setHover(false);
}}
>
...
</li>
);
})}
</ul>
{/* 복제리스트 */}
<ul className={`${listClasses} ${styles.original}`}>
{airplaneList.map((airplane, index) => {
const [hover, setHover] = useState(false);
return (
<li
key={`clone : ${index}`}
className={styles.airplane}
onMouseEnter={() => {
onStop();
setHover(true);
}}
onMouseLeave={() => {
onRun();
setHover(false);
}}
>
...
</li>
);
})}
</ul>
</div>
);
stop 상태를 통해 클래스를 토글할 수 있도록 하고,
동시에 airplane의 hover 상태는 각각 map된 컴포넌트 요소에서 개인적으로 다룰 수 있도록 한다.
이렇게 되면 BannerList의 상태변화에 따라 각 요소들의 애니메이션 정보 등이 Virtual DOM에 직접 저장되므로,
애니메이션이 초기화되는 위험성을 해결할 수 있다.
'--- Project --- > CharFlyer : 캐플라이어' 카테고리의 다른 글
9/25 - margin : auto (0) | 2023.09.25 |
---|---|
9/24 - 의도한 스타일 만들기 (0) | 2023.09.24 |
9/20 - 부모보다 큰 자식 나열하기 (0) | 2023.09.20 |
9/19 - BannerSection API 연결 (0) | 2023.09.19 |
[개발] Next.js 13 mongoose API 만들기 (TypeScript) (0) | 2023.09.17 |