일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 무선랜카드 교체
- react-dom
- scroll-snap
- HTML
- 유한 상태 머신
- live share
- Shadow DOM
- webpack
- Grafana
- 우아한테크코스 레벨3
- 데모데이
- typescript
- browserslist
- 터치 스와이프
- swiper
- Docker
- RTL8852BE
- Prometheus
- CSS
- web component
- github Actions
- custom element
- fastify
- AX210
- 우아한테크코스
- 우테코
- docker-compose
- GitHub Pages
- javascript
- react
- Today
- Total
IT일상
React Context를 쓰면 전체가 리렌더링된다고? 본문
리뉴얼 된 블로그로 보기: https://solo5star.dev/posts/42/
const App = () => {
const [mousePosition, setMousePosition] = useState<[number, number]>([0, 0]);
// 마우스 움직임을 감지하여 mousePosition 상태 계속 업데이트
useEffect(() => {
document.addEventListener('mousemove', (event) => {
setMousePosition([event.clientX, event.clientY]);
});
}, []);
return (
<MouseMovementContext.Provider value={mouseMovementContextValue}>
<Header />
<Article title="오늘도 즐거운 하루" content="오늘 새벽에 좀 많이 추워서 그런지 아침에 힘들었어요. 일교차가 크니 조심하도록 합시다.">
<MousePositionMeter />
</Article>
<Article title="냉동피자가 4천원이면 꽤 괜찮은데?" content="피자치즈 얹어먹으면 더더욱 맛있습니다. 근데 집에 피자치즈가 있어야 합니다." />
<Article title="힘들었던 일주일이 드디어 끝" content="너무 빡센거같다. 야근도 적당히 해야지!!" />
<Footer />
</MouseMovementContext.Provider>
);
};
이런 구조의 앱이 있다고 가정해봅시다.
MouseMovementContext라는게 있고 마우스가 움직이면 상태를 계속 업데이트합니다.
MouseMovementContext로 부터 마우스의 위치 상태 값을 받는 컴포넌트는 MousePositionMeter 밖에 없습니다.
과연 Header, Article, Footer 모두 리-렌더링 될까요?
네! 전부 리-렌더링 됩니다. App에서 setState를 함으로서 App에서 리-렌더링이 발생하는데, 함수에서 또 Header, Article, Footer를 호출하여 리-렌더링하는 식이기 때문에
결과적으로 전체가 리-렌더링되고 있습니다.
내용물 끌어올리기
내용물 끌어올리기에 대한 이해는 이 글이 많이 참고가 되었습니다. 꼭 읽어보시면 좋겠습니다.
const MouseMovementProvider = ({ children }: PropsWithChildren) => {
const [mousePosition, setMousePosition] = useState<[number, number]>([0, 0]);
// 마우스 움직임을 감지하여 mousePosition 상태 계속 업데이트
useEffect(() => {
document.addEventListener('mousemove', (event) => {
setMousePosition([event.clientX, event.clientY]);
});
}, []);
return (
<MouseMovementContext.Provider value={mouseMovementContextValue}>
{children}
</MouseMovementContext.Provider>
);
}
const App = () => {
return (
<MouseMovementProvider>
<Header />
<Article title="오늘도 즐거운 하루" content="오늘 새벽에 좀 많이 추워서 그런지 아침에 힘들었어요. 일교차가 크니 조심하도록 합시다.">
<MousePositionMeter />
</Article>
<Article title="냉동피자가 4천원이면 꽤 괜찮은데?" content="피자치즈 얹어먹으면 더더욱 맛있습니다. 근데 집에 피자치즈가 있어야 합니다." />
<Article title="힘들었던 일주일이 드디어 끝" content="너무 빡센거같다. 야근도 적당히 해야지!!" />
<Footer />
</MouseMovementProvider>
);
};
내용물 끌어올리기를 적용하면 이와 같은 코드가 됩니다.
Header, Article, Footer를 children을 통해서 전달해주는데, 이 컴포넌트들은 App이 렌더링하여 MouseMovementProvider에 넘겨주기 때문에 App이 리-렌더링 되지 않는 이상 Header, Article, Footer은 리-렌더링 되지 않습니다.
즉 MouseMovementProvider가 계속 리-렌더링되어도 Header, Article, Footer는 리-렌더링 되지 않습니다.
MousePositionMeter 에서는 useContext(MouseMovementContext) 를 사용하기 때문에 리-렌더링이 계속 되고 있네요.
1) Context를 사용했을 때 전체가 리-렌더링 된다는 것과
2) 전체 리-렌더링을 피할 수 있는 방법 (내용물 끌어올리기)
이렇게 알아보았습니다. React Context API를 쓰는데 있어 참고가 되면 좋겠습니다!
Code Sandbox
전체 코드(더럽습니다)
주의: 코드를 대충 작성해서 더럽습니다... 깊게 이해하려 시도하지 마세요.
App.tsx
import React, { useState, useEffect, useMemo, useContext, forwardRef, useRef } from 'react';
import { PropsWithChildren } from 'react';
import './App.css';
const withDetectRender = <P,>(name: string, fc: React.FC<P>): React.FC<P> => {
return (...args: Parameters<typeof fc>): ReturnType<typeof fc> => {
const ref = useRef<HTMLDivElement>(null);
console.log('render', name);
const $div = ref.current;
if ($div) {
$div.classList.remove('rerender');
$div.offsetHeight;
$div.classList.add('rerender');
}
return (
<div className="render-unit rerender" data-name={name} ref={ref}>
{fc(...args)}
</div>
)
};
}
const ArticleContent = withDetectRender('ArticleContent', (props: { content: string }) => {
const {content} = props;
return <p>{content}</p>;
});
const Article: React.FC<PropsWithChildren<{ title: string, content: string }>> = withDetectRender('Article', (props) => {
const { title, content, children } = props;
return (
<article style={{ width: '600px' }}>
<h1>{title}</h1>
<ArticleContent content={content} />
{children}
</article>
);
});
export const MouseMovementContext = React.createContext<{
mousePosition: [number, number];
}>({
mousePosition: [0, 0]
});
const MousePositionMeter = withDetectRender('MousePointerMeter', () => {
const { mousePosition } = useContext(MouseMovementContext);
return (
<p>YOUR MOUSE POSITION: [{mousePosition.join(', ')}]</p>
);
});
const Footer = withDetectRender('Footer', () => {
return (
<footer>
<MousePositionMeter />
</footer>
);
});
const Button: React.FC<{ name: string }> = withDetectRender('Button', (props) => {
const { name } = props;
return (
<button>{name}</button>
);
})
const Header = withDetectRender('Header', () => {
return (
<header>
<p>쓸데없는 일기 적는 앱</p>
<ul style={{ display: 'flex' }}>
<Button name="홈" />
<Button name="글 목록" />
<Button name="사이트 정보" />
</ul>
</header>
)
})
const MouseMovementProvider: React.FC<PropsWithChildren> = withDetectRender('MouseMovementProvider', (props) => {
const { children } = props;
const [mousePosition, setMousePosition] = useState<[number, number]>([0, 0]);
useEffect(() => {
document.addEventListener('mousemove', (event) => setMousePosition([event.clientX, event.clientY]));
}, []);
const mouseMovementContextValue = useMemo(() => ({ mousePosition }), [mousePosition]);
return (
<MouseMovementContext.Provider value={mouseMovementContextValue}>
{children}
</MouseMovementContext.Provider>
)
})
const App = withDetectRender('App', () => {
return (
<MouseMovementProvider>
<Header />
<Article title="오늘도 즐거운 하루" content="오늘 새벽에 좀 많이 추워서 그런지 아침에 힘들었어요. 일교차가 크니 조심하도록 합시다.">
<MousePositionMeter />
</Article>
<Article title="냉동피자가 4천원이면 꽤 괜찮은데?" content="피자치즈 얹어먹으면 더더욱 맛있습니다. 근데 집에 피자치즈가 있어야 합니다." />
<Article title="힘들었던 일주일이 드디어 끝" content="너무 빡센거같다. 야근도 적당히 해야지!!" />
<Footer />
</MouseMovementProvider>
);
});
export default App;
App.css
p, h1, ul {
margin: 0;
padding: 0;
}
@keyframes rerender-border {
from {
border-color: rgb(0, 255, 42);
}
to {
color: initial;
}
}
@keyframes rerender-label {
from { color: rgb(0, 255, 42); }
to { color: initial; }
}
body {
transition: transform 0.5s;
transform: skew(0, -10deg);
transform-origin: 50% 50%;
transform-style: preserve-3d;
}
body * {
transform-style: preserve-3d;
}
.render-unit {
margin: 4px;
margin-top: 24px;
padding: 4px;
position: relative;
border: 1px dashed hsl(0, 0%, 80%);
transform-origin: top left;
transition: transform 0.5s;
}
body:hover .render-unit {
transform: translateX(10px) translateY(10px);
}
.render-unit.rerender {
animation: rerender-border 0.5s linear;
animation-fill-mode: forwards;
}
.render-unit.rerender::before {
display: inline-block;
content: '⚛' attr(data-name);
font-size: 12px;
position: absolute;
left: 0;
bottom: 100%;
animation: rerender-label 0.5s linear;
animation-fill-mode: forwards;
}
'프론트엔드' 카테고리의 다른 글
React SSR (서버 사이드 렌더링) 얕게 시작해보기 (React.hydrateRoot) (4) | 2023.05.05 |
---|---|
webpack config 자동완성 되도록 설정하기 (4) | 2023.05.05 |
javascript에서 RGB ↔ HSL 변환하기 (2) | 2023.04.30 |
CSS로 카드 뒤집는 3D 애니메이션 만들기 (6) | 2023.04.29 |
javascript에서 한글에서 종성(받침)이 있는지 체크하는 방법 (0) | 2023.04.25 |