티스토리 뷰
Hook
React에서 기존에 사용하던 Class형 컴포넌트에서 사용되던 메소드들 없이도 함수형 컴포넌트에서 Hook을 통해 상태 관리와 여러 기능을 사용할 수 있도록 만든 기능입니다.
Hook 탄생 배경
React 컴포넌트는 클래스형 컴포넌트 & 함수형 컴포넌트로 나뉘었는데요. 기존의 개발 방식은 일반적으로 함수형 컴포넌트를 주로 사용하되, state나 life cycle method를 사용해야 할 때에만 클래스형 컴포넌트를 사용하는 방식이었습니다.
그렇게 사용했던 이유는 클래스형 컴포넌트의 어려운 클래스 문법, 어려운 축소, 어려운 로직의 재사용성이라는 단점이 존재하지만, state나 life cycle method를 사용하기 위해서는 클래스형 컴포넌트를 사용해야만 했었기 때문인데요.
이러한 불편함을 해소하고자!
Hooks가 등장했고, 함수형 컴포넌트에서도 state와 life cycle method를 사용할 수 있게 된것이죠!
덕분에 클래스형 컴포넌트의 단점 극복 + 상태 관리 + 생명주기 함수 사용까지도 가능해진 것입니다 👍🏻👍🏻
State & Life Cycle Method
State
State는 컴포넌트 내에서 관리되는 데이터입니다. 컴포넌트의 동작과 상호작용을 제어하고 컴포넌트가 렌더링될 때마다 변경되는 값을 저장하는 데 사용됩니다. state는 컴포넌트 내에서 변경이 가능합니다.
state는 컴포넌트의 동적인 부분을 나타내며, 사용자의 상호작용, 서버로부터의 데이터 로딩, 시간에 따른 변화 등과 같은 변동 사항을 표현하기 위해 사용합니다.
✨✨✨
⭐ state는 렌더링의 트리거하는 주요한 역할을 하는데요!
state가 변경되지 않는다면, 불필요한 렌더링을 피하고, 성능을 개선하기 위해 UI 업데이트 수행이 일어나지 않을 것입니다.
✨✨✨
참고로, setState를 통해 React에서 상태 변경을 알리도록 되어 있어 직접 값을 변경한 경우 렌더링이 되지 않는 것입니다.
Life Cycle Method(생명주기 메서드)
컴포넌트가 브라우저 상에 나타나고, 업데이트되고, 사라지게 될 때 호출하는 메서드입니다.
State 최적화를 위한 방법
State를 최적화한다는 개념
state는 렌더링을 트리거하는 주요한 역할을 한다고 했었는데요! 이말은 곧, UI를 업데이트하는 작업과 연관되었다고도 할 수 있습니다.
⭐ 즉, UI 업데이트하는 데 비용이 많이 드는 DOM 작업 수를 최소화하는 것이 필요합니다.
State 업데이트 최적화가 중요한 이유
⭐ 성능 향상과 직접적인 연관
- 불필요한 렌더링 방지
- 화면에 변화가 없는데도 불필요하게 컴포넌트를 다시 그리는 작업을 의미
- 불필요한 렌더링은 불필요한 리소스 사용을 초래할 수 있다
- 동일한 데이터를 중복해서 서버에게 요청하는 경우 네트워크 대역폭 낭비 및 서버의 부하까지도 이어질 수 있다
- 가상 DOM 비교 최소화
- 가상 DOM 비교는 성능에 영향을 주는 계산적인 비용이 따르는 작업
- 따라서 state 업데이트를 최적화하여 가상 DOM 비교를 최소화하면 React의 업데이트 성능 향상
방법1. Independent child, Careless parent
Render Waterfall
구성 요소의 부모가 렌더링되면 모든 자식도 렌더링된다는 말을 일컫는데요. 부모 컴포넌트로 인한 복잡한 자식 컴포넌트들로 인해 불필요하게 다른 자식 컴포넌트들까지 렌더링되는 경우가 생기게 됩니다.
쉽게 말하면 부모 - 자식1, 자식2 가 있다고 했을 때 자식1과만 상관이 있는 상태를 부모에서 선언하면 자식1에서 상태 업데이트를 했다면 부모의 상태가 업데이트된 것이기 때문에 해당 상태와 전혀 상관 없는 자식2까지도 리렌더링이 일어납니다. 자식2는 다시 그려질 필요가 전혀 없는데도 말이죠.
상태는 독립된 작은 부분에서만 관리하자
부모 컴포넌트는 자식 컴포넌트의 상태 변경에 신경쓰지 않고, 자식 컴포넌트가 자체적으로 상태를 관리하도록 해야합니다.
→ 자식 컴포넌트의 상태 변경과 관련된 로직을 신경쓰지 않고,
→ 자식 컴포넌트를 렌더링하는 역할에 집중
위 사진을 보면 무슨 말인지 이해하기 쉬울 겁니다!
실제로 위 방법이 렌더링 최적화에 도움이 되는지 실험을 해보았는데요.
📌 실험 내용
✔️ 부모 컴포넌트 - Child1 & Child2가 있다.
1️⃣ 상황1 : Child1와만 관련이 되어있는 상태를 부모 컴포넌트에서 관리
2️⃣ 상황2 : Child1와만 관련이 있는 상태를 Child1에서만 관리
※ Child2를 통해 불필요한 렌더링으로 인한 성능 차이를 유의미하게 측정하기 위해 Child2 컴포넌트에는 고화질의 사진이 포함되어 있다.
⚙️성능 측정은 `Profiler`를 통해 진행 → Render duration 확인
// Bad
const Parent = () => {
const [isOpen, setIsOpen] = useState(false)
return (
<>
<h1>Bad</h1>
<button onClick={() => setIsOpen(true)}>업데이트</button>
{isOpen && <Child1 />}
<Child2 />
</>
)
}
export default Parent
// Good
const Parent = () => {
return (
<>
<h1>Good</h1>
<ButtonWithChild />
<Child2 />
</>
)
}
export default Parent
📌 실험 결과
<Rendering duration>
경우1: 1.5ms
경우2: 0.8ms
예상대로, 불필요한 리렌더링을 줄여 렌더링 시간을 줄일 수 있었다.
방법2. Minimal states, Minimal render
최소한으로 state를 선언하도록 노력해야 하는데요. 특히 한 state로 파생되어 사용될 수 있는 값들이 존재한다면 따로 또 state를 선언하는 방법이 아니라 기존 state를 가지고 화면을 렌더링할 수 있도록 해야 합니다.
// Bad
const [count, setCount] = useState(0)
const [isEven, setIsEven] = useState(false)
const [isPrime, setIsPrime] = useState(false)
const [isPositive, setIsPositive] = useState(false)
const [isMultipleOfFive, setIsMultipleOfFive] = useState(false)
return (
<>
<h1>To Much State 😈</h1>
<h3>count: {count}</h3>
<h3>Is Even : {isEven ? 'Yes' : 'No'}</h3>
<h3>Is Prime : {isPrime ? 'Yes' : 'No'}</h3>
<h3>Is Positive: {isPositive ? 'Yes' : 'No'}</h3>
<h3>Is Multiple of Five: {isMultipleOfFive ? 'Yes' : 'No'}</h3>
<hr />
<button onClick={increment}>증가</button>
</>
)
// Good
const [count, setCount] = useState(0)
return (
<>
<h1>Minimal State 👶🏻</h1>
<h3>count: {count}</h3>
<h3>Is Even : {count % 2 === 0 ? 'Yes' : 'No'}</h3>
<h3>Is Prime : {count % 2 !== 0 ? 'Yes' : 'No'}</h3>
<h3>Is Positive: {count > 0 ? 'Yes' : 'No'}</h3>
<h3>Is Multiple of Five: {count % 5 === 0 ? 'Yes' : 'No'}</h3>
<hr />
<button onClick={increment}>증가</button>
</>
)
방법4. React.memo
부모 - 자식1, 자식2, 자식3 컴포넌트가 있다고 했을 때,
만약 부모 컴포넌트에서 관리하고 있는 state를 자식1과 자식2에게 넘겨주어야 한다면
사실 자식3은 부모 컴포넌트에서 업데이트가 일어난다고 한들 다시 그려질 필요가 없습니다.
이럴 때 자식3에 React.memo를 적용해주게 되면 불필요한 리렌더링을 막을 수 있습니다.
렌더링 최적화 찾아보면 Memoization 많이 나오던데..
Memoization
- 메모이제이션은 메모리 공간을 더 많이 사용하는 대가로 컴퓨터 프로그램 속도를 높이는 데 사용되는 최적화 기술
- 메모이제이션을 통한 속도 향상은 동일한 매개 변수가 제공될 때 결과의 반복 계산을 피한다.
- 대신에 캐시된 결과를 사용한다.
- 캐시된 결과가 추가 공간을 차지하기 때문에 메모리 공간 사용량 증가
React에서 Memoizaition
- 복잡한 구성 요소를 다시 렌더링하는 경우 → 성능 문제 → 사용자 경험에 영향
- 동일한 입력으로 다시 실행할 때마다 값을 다시 계산하지 않고 캐시된 값을 사용하고 싶다면
- ex. props가 이전과 동일하다면 굳이 다시 처음부터 화면을 그릴 필요가 없음
- 초기 렌더링 결과를 캡처하고 나중에 사용할 수 있도록 메모리에 캐시 가능
⭐ 웹 성능 향상에 도움
React에서 Memoization을 활용하면 유용한 경우
- 주로 복잡한 계산, 연산, 데이터 변화 등의 경우에 유용
- 불필요한 연산을 줄이고 성능을 향상시키는 데 도움
- 계산 비용이 높은 연산
- 계산 비용이 높은 연산을 수행해야 하는 경우
- 메모이제이션을 사용하여 연산 결과를 캐시 가능
- 이를 통해 동일한 입력에 대해 다시 계산하지 않고 이전에 계산된 결과를 재사용 가능
- 그런데, 수천 개의 항목에 대해 루프를 수행하거나 팩토리얼 계산을 수행하지 않는 한 비용이 많이 들지 않을 수 있다
- 렌더링 성능 최적화
- 컴포넌트의 state나 props가 변경되지 않았다면
- 이전 결과를 다시 계산하지 않고 이전에 계산된 결과를 재사용하여 불필요한 렌더링 방지
React에서 모든 것을 메모해야 할까? "No!"
⭐ 메모이제이션은 무료가 아니다.
⭐ 무분별하거나 과도한 메모이제이션은 그만한 가치가 없을 수 있다.
- 메모이제이션을 추가할 때 3가지 주요 비용이 발생
- 메모리 사용량 증가
- 너무 많은 것을 메모하면 메모리 사용량 관리에 어려움
- 메모리가 부족해지면 컴포넌트의 렌더링과 상태 업데이트에 소요되는 시간 증가할 수 있다
- 메모리 누수 가능성
- 메모이제이션은 결과를 캐시하여 재사용
- 이를 관리하지 않고 사용하는 경우 메모리 누수가 발생할 수 있다
- 캐시된 결과가 더 이상 필요하지 않은 경우에도 계속해서 메모리에 남아있게 되는 경우
- 메모이제이션을 위한 추가적인 코드로, 코드 복잡성 증가
- 메모이제이션은 캐시를 관리하고 관련된 종속성을 처리하는 추가적인 로직을 도입하게 된다
- 너무 많이 사용하면 코드를 이해하기 어려워질 수 있고, 디버깅과 유지보수에 어려움이 생길 수 있다
- 메모리 사용량 증가
어떤 경우에 메모이제이션을 피해야 할까?
- 최적화하려는 계산의 비용이 크지 않은 경우
- 이러한 경우 메모이제이션 할 때 발생하는 오버헤드가 이점보다 클 수 있다.
- React 공식 홈페이지에서는 1ms 이상 걸리는 경우 메모해두는 것이 좋다고 이야기
- 메모이제이션이 필요한지 확실하지 않은 경우
- 우선 없이 작업을 하고, 문제가 발생하면 점진적으로 최적화를 적용하는 방향이 올바르다
- 의존성 배열이 너무 자주 변경되는 경우
- 계산되는 경우가 많으면 성능적인 이점을 얻을 수 없다
이번 글에서는 State 자체가 무엇인지, State를 최적화한다는 게 무슨 개념인지, 그리고 State를 최적화하기 위한 방법까지도 알아보았는데요. 최적화 방법으로 많이 소개되는 메모이제이션에 대해서도 이야기했습니다. 메모이제이션은 바로 도입하기보다는 추후에 도입을 하여 여러 연산량을 비교하여 도입을 신중히 결정해야 할 것입니다.
참고문서
- React - Hook
- [React] 리액트 훅(Hook)에 대해서 알아보자
- React - State and Lifecycle
- [React] 함수형 컴포넌트 생명주기(lifecycle) 이해하기
- 21 Performance Optimization Techniques for React Apps
- Efficient React Components: A Guide to Optimizing React Performance
- React re-renders guide: everything, all at once
- React - State 끌어올리기
- React: Lifting state up is killing your app
- How and When tk Memoize Your React Application
- What is Memoization in React?
- Heavy Computation Made Lighter: React Memoization
'프론트엔드 > React' 카테고리의 다른 글
[React] 쓸 줄만 알았던 react-router-dom v6 동작원리 (0) | 2023.06.21 |
---|---|
[React] React Testing Library과 관련된 일반적인 실수 (0) | 2023.06.15 |
[React] Automatic Batching - 일괄 처리 (0) | 2023.06.14 |
[React] Lazy loading과 Code splitting - 적당히 적당히 가져오자 (0) | 2023.06.13 |
[React] Virtual DOM과 ✨React 렌더링 동작 원리✨ with Fiber (2) | 2023.06.12 |
- Total
- Today
- Yesterday
- CSS
- 머신러닝
- react
- 리액트
- 자바스크립트
- 프론트엔드 공부
- 리액트 훅
- rtl
- styled-components
- 스타일 컴포넌트 styled-components
- TypeScript
- Python
- 프론트엔드 기초
- react-query
- 타입스크립트
- jest
- 프로젝트 회고
- 딥러닝
- 디프만
- JSP
- 데이터분석
- 파이썬
- HTML
- frontend
- 자바
- 인프런
- next.js
- testing
- 프론트엔드
- 자바스크립트 기초
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |