- Today
- Total
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- 리액트
- next.js toast
- useRouter
- sessionStorage
- react
- svgr
- 자바스크립트
- 리액트 라이프사이클
- resolved to branch.
- useformstatus
- styled-component
- 리액트 알림
- NextJS
- 사회복무요원 훈련소
- no-use-before-define
- server action
- angular
- 오블완
- react life sycle
- 훈련소 후기
- 비동기 병렬처리
- query param
- 공익 훈련소
- 오라클클라우드
- The above error occurred in the
- localStorage
- 자바스크립트 순수함수
- 훈련소
- react toast
- 산업기능요원 훈련소
아 그거 뭐였지
[React] 리액트 렌더링 최적화에대해 알아보자 .with (useMemo, useCallback, React.mamo) 본문
[React] 리액트 렌더링 최적화에대해 알아보자 .with (useMemo, useCallback, React.mamo)
승발자 2023. 6. 4. 23:46
리액트 렌더링 최적화에 대해 ARABOZA...
리액트 렌더링 최적화에 대해 알고있다고 생각했지만 설명해보라고하면 쉽사리 입이 떨어지지않는다.
이번기회에 한번 제대로 알아보고 가자
렌더링 최적화에 대해 알아보기전
리액트에서 렌더링 된다는 것은 무엇을 의미할까?
간단하게 의미만 살펴보자면
함수가 호출되면 함수는 내부 로직을 실행하고 리액트 element들을 반환한다.
이것이 리액트에서의 렌더링이라고 볼수있다.
호출되면 a라는 변수에 a값을 할당하고 <div>렌더링</div> 이라는 리액트 element를 반환한는 짧은 함수이다.
function App(){
const a = "a"
return <div>렌더링</div>
}
렌더링 최적화에대해 예시코드를 가져왔다.
function Parent() {
const [changeValue,setChangeValue] = useState(1);
const handleClick = ()=>{
console.log("click");
}
return (
<>
<FirstChild changeValue={changeValue} />
<SecondChild handleClick={handleClick} />
<button onClick={()=>setChangeValue(prev=>prev+1)}>ChangeValue</button>
</>
)
}
function FirshChild({changeValue}){
console.log("FirstChild Rendering");
return(
<div>{changeValue}</div>
)
}
function SecondChild({handleClick}){
console.log("SecondChild Rendering");
const bigArr = [...new Array(50000)].map((_, i) => i + 1);
return(
{
bigArr.map(item=>(<div>{item}<div>))
}
<div onClick={handleClick}> SecondChild </div>
)
}
Parent 컴포넌트에서 changeValue값이 변경되면
FirstChild와 SecondChild가 리렌더링되게된다.
SecondChild 컴포넌트는 handleClick함수만을 props로 받고있고 값이 변경된것이 없는데
왜 리렌더링이될까?
바로 Parent함수에서 handleClick함수는 렌더링될때마다 새로운 함수를 만들기 때문이다.
SecondChild 컴포넌트 입장에서는 이전의 handleClick함수와 리렌더링후 전달받은 handleClick 함수의
참조값이 변경되어 props가 다르다고 판단하고 리렌더링이 되는것이다.
그렇다면?
함수의 참조값을 메모이제이션 해두면 같은 함수라 판단하고 리렌더링이 일어나지 않을것이다.
함수의 참조값을 메모이제이션 하기위해서는 useCallback hook을 사용하면된다.
useCallback?
공식문서에는 아래와같이설명한다.
useCallback is a React Hook that lets you cache a function definition between re-renders.
- 리렌더링시 함수를 캐싱하는 리액트 훅이다.
자바스크립트에서 함수는 객체이다. 객체라는것은 참조 타입 데이터이고 참조값을 가진다.
함수 또한 참조값을 가진다는 의미이다. 이 참조값을 기억해주는 것이 useCallback이다.
해결?
그렇다면 함수의 참조값을 기억하면 리렌더링이 발생하지 않을수있다. 바로 useCallback을 적용해보자.
handliClick함수에 useCallback을 적용하여 동일한 함수값을 가지게 한 예제이다.
function Parent(){
const handleClick = useCallback(()=>console.log("click"),[])
}
useCallback으로 handleClick이 동일한 참조값을 가지게 변경했으니
SecondChild 컴포넌트는 리렌더링 되지 않을것이라 기대할수있다.
하지만 아쉽게도 SecondChild 컴포넌트는 리렌더링 된다.
이유를 알아보도록 하자.
function Parent(){
return React.createElement(
React.createElement(FirstChild,{changeValue:changeValue}),
React.createElement(SecondChild,{handleClick:handleClick})
)
}
Parent컴포넌트를 babel로 컴파일한 코드를 살펴보면
jsx를 React.createElement를 사용하여 SecondChild컴포넌트를 반환한다.
Parent컴포넌트가 리렌더링 되면 내부의 로직들이 실행된다.
이때 React.createElement도 실행되고 SecondChild의 React.createElement도 실행되게된다.
handleClick의 참조값이 같을뿐 React.createElement는 실행이된다.
그럼 useCallback을 사용한것이 의미가 없는걸까?
렌더링 프로세스에서의 이점은있다.
Render Phase는 실행이되지만 useCallback으로 인해 handleClick의 함수 참조값이 같으므로
Commit Phase는 실행이되지않는다.
렌더링이 됐지만 렌더링이 안됐다(?)
갑자기 튀어나온 Render Phase와 Commit Phase는 뭔가?
렌더링 프로세스를 보며 Render Phase와 Commit Phase에 대해서 알아보도록하자.
리액트의 렌더링 프로세스는 다음과같다.
1. 렌더링 트리거가 발생했을때 (초기렌더링, 리렌더링시에는 state가 변경됐거나 props값이 바뀌거나)
2. 변경이 필요한 부분 체크 (Render Phase)
3. 변경할곳을 직접 적용 (Commit Phase)
Render Phase
컴포넌트를 호출하여 리액트 element를 반환하고 새로운 VirtualDOM을 생성한다.
첫번째 렌더링이 아닌경우 재조정을 거친후 RealDOM에 변경이 필요한 DOM들을 체크한다.
재조정(reconciliation) : 이전 VirtualDOM과 변경될 VirtualDOM을 비교하는 과정이다.
Commit Phase
Reander Phase에서 변경이 필요한 DOM들을 RealDom에 반영해주는 과정이다.
만약 변경이 가능한 DOM이 없다면 Commit Phase는 스킵된다.
리액트에서는 렌더링이 일어날때마다
Render Pahse와 Commit Pahse를 거치게된다.
useCallback을 사용했을때는 Props의 참조값이 같기때문에 Reander Phase의 재조정 과정에서
RealDOM에 변경이 필요하지 않다고 판단한다.
때문에 Reander Phase만 실행되고 Commit Phase는 실행이 되지않는다라는 점에서
렌더링 프로세스의 최적화가 유의미 하다고 볼수있다.
Render Phase도 막기위해서는 React.memo를 사용하면 된다.
React.memo
React.memo는 전달받은 props가 이전 props와 비교했을때 같으면 컴포넌트의 리렌더링을 막아주고
마지막으로 렌더링된 컴포넌트를 재사용하는 고차컴포넌트이다.
props의 값이 같다면 컴포넌트를 실행시키지 않고 이전에 렌더링된 컴포넌트를 재사용한다.
React.memo는 props를 비교할때 얕은복사를 통해 비교한다.
원시 타입 데이터는 원시값이 같은지 비교하고 ex) const aString = “a” 일경우 aStrind의 값이 a인지 비교
참조 타입 데이터는 참조값이 같은지 비교한다. ex) const obj ={name:”홍길동”} 일경우 obj의 name
이 홍길동인지 비교하는것이 아니라 obj가 어떤 메모리 주소값을 참조하고있는지 비교하는것
function SecondChild({handleClick}){
console.log("SecondChild Rendering");
...
return(
...
)
}
export default React.memo(SecondChild)
SecondChild 컴포넌트를 React.memo를 사용해주게되면
props의 값인 handleClick에 대해서 이전값과 현재값이 같은지 비교하게된다.
이전에 useCallback을 사용해주었기때문에 handleClick은
Parent컴포넌트가 리렌더링되도 참조값이 같게된다.
따라서 props값이 같기때문에 React.memo에 의해 렌더링을 발생시키지 않고 마지막으로
렌더링된 컴포넌를 재사용하게된다.
객체를 넘겨준다면?
function Parent(){
const obj = {
name:"홍길동",
age:20
}
return(
<>
<FirstChild/>
<SecondChild obj={obj} />
</>
)
}
객체도 참조 타입의 데이터이기 때문에 Parent컴포넌트가 렌더링될때마다 obj 객체를 새롭게 만든다.
따라서 SecondChild컴포넌트는 React.memo를 적용시켜놨더라도 obj의 참조값이 바뀌기 때문에
React.memo는 작동하지않는다.
객체를 메모이제이션하고 리렌더링 시키지않으려면 useMemo를 사용하면된다.
useMemo
useMemo는 useCallback과 달리 함수 호출 결과 값을 기억하는 hook이다.
function Parent(){
const obj = {
nane:"홍길동",
age:20
}
const memoObj = useMemo(()=>obj,[]);
return(
<>
<FirstChild/>
<SecondChild obj={memoObj} />
</>
)
}
obj 객체에 useMemo를 사용해주면 의존성배열의 값이 바뀌기 전에는 obj객체는 새롭게 만들어지지 않는다.
즉 참조값이 변경되지않을수있는것이다.
따라서 memoObj 를 SecondChild 컴포넌트에 전달해준다면 memoObj의 참조값이 같기때문에
SecondChild컴포넌트는 리렌더링 되지 않을것이다.
리액트 공식문서에서는 아래와 같이 말하고있다.
Don’t optimize prematurely! do it when needed
조기에 최적화하지말라. 필요한 상황이 생기면 그때 최적화를 하자.
위의 언급한 hook들도 결국 비용이 발생한다. useMemo,useCallback,React.memo 를 무작정 사용하려 하지말고 그전에 코드단에서 최적화를 할수있는 방법이 없는지 먼저 생각해보는 습관을 들여보자.
참고자료
[10분 테코톡] 앨버의 리액트 렌더링 최적화 - YouTube
'Front-End' 카테고리의 다른 글
[React] React + Vite(^v4) 환경에서 svg 설정하기 (0) | 2024.03.17 |
---|---|
[NextJS] 새로워진 Image태그를 알아보자 (image layout fill) (1) | 2023.10.17 |
[React] React Life-Sycle ( 클래스형, 함수형), 리액트 라이프 사이클 알아보기 (0) | 2023.05.14 |
[NextJS] react-tostify로 toast알림 구현하기 (.with styled-component, customHooks) (0) | 2023.04.05 |
[NextJS] 로그인 판별하여 페이지 리다이렉트 시켜주기 .with GetServerSideProps (0) | 2023.03.28 |