[React] 리렌더를 멈춰줘! Memoization, 속도를 더 빠르게! Preload/Prefetch

badassong·2022년 12월 15일
0

React

목록 보기
16/56
post-thumbnail

Memoization

위키피디아에 따르면 메모이제이션(memoization)은 컴퓨터가 동일한 계산을 반복해야 할 때 이전에 계산한 값을 메모리에 저장함으로서 동일한 계산을 하지 않도록 하여 속도를 높이는 기술이다. 보통 애플리케이션의 최적화를 위해 사용된다.
이는 서비스 성능 최적화를 위해 리렌더를 막을 때도 사용할 수 있다.

useCallback()

const memoizedCallback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);

useCallback은 메모이제이션 된 콜백을 반환한다. 컴포넌트 내부에 있는 위치해있는 컴포넌트가 렌더링 될 때마다 다시 함수를 생성한다. 하지만 useCallback으로 감싸주게 되면 첫 렌더할 때에만 생성하고 그 이후에는 함수를 기억하고 있어 재생성하지 않는다.

dependency(의존성) 배열에는 어떠한 값이 변경되었을 때 다시 생성해준다는 뜻으로 새로운 값으로 함수를 실행해야 한다면 반드시 그 값을 해당 의존성 배열에 넣어줘야 한다.

useMemo()

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

useMemo는 메모이제이션 된 값을 반환한다.

렌더링하는 과정에서 특정 값이 바뀌었을 때만 계산을 실행하고, 원하는 값이 바뀌지 않았다면 이전에 계산한 그 값을 그대로 사용한다. useCallback과 마찬가지로 dependency 배열에 값을 넘긴다.


이렇게 불필요한 값들이 지속적으로 다시 만들어지지 않도록 유지시켜주는 hooks가 바로 useMemo(),useCallback()이다.

하지만 그렇다고 해서 모든 컴포넌트에 메모를 거는 것은 좋지 않은 방법이다!
useMemo나 useCallback에 dependency 배열이 너무 길어지면 오히려 유지보수를 깨뜨릴 수 있다!
ex) dependency 배열의 인자가 2개를 초과할 때는 그냥 리렌더를 해주는 것이 더 좋다!

🧡유용한 개발자 도구!
설치는 구글 웹스토어에서 진행하면 된다.
1. Apollo Client Devtools
→ 설치후 app.tsx에서 client 설정 부분에 connectToDevTools : true로 설정해줘야 한다.
2.wappalyzer
→특정 사이트에 들어가시면 해당 사이트가 사용한 스택을 분석해준다.

memo

memo라는 기능은 react에서 제공하고 있는 기능입니다. 따라서 react에서 import해서 사용하면 된다.

// memoization 폴더 _ child 파일
import {memo} from "react"

const MemoizationChildPage = ()=>{
	console.log("자식 컴포넌트가 렌더링 됩니다.")


	return(
		<div>  
			<div>================<div>
			<h1>이것은 자식 컴포넌트입니다.</h1>
			<div>================<div>
		</div>
	)
}
export default memo(MemoizationChildPage)

이렇게 memo를 사용하고 state카운트를 클릭하면 프리젠터는 렌더링이 되지않아 콘솔도 찍히지 않을 뿐더러 리액트 툴에도 보이지 않는다.
memo를 호출하는 부분을 잘 보시면 HOC의 일종임을 알 수 있다.

map과 memo의 관계

import { useState } from "react"
import Word from "./02-child"

export default function MemoizationParentPage(){
	const [data,setData] = useState("철수는 오늘 점심을 맛있게 먹었습니다.")

	const onClickChange = ()=>{
		setData("영희는 오늘 저녁을 맛없게 먹었습니다.")
	}
	return(
		<>
			{data.split("").map((el)=>(
				<Word key={index} el={el}/>
			))}
			<button>체인지</button>
		</>
	)
}
import { memo } from "react"

export default function Word(props: any){
	console.log("자식이 렌더링 됩니다!", props.el)
	return <span>{props.el}</span>
}

export default memo(Word)

이렇게 memo를 걸어두면 처음의 값이 기억되어 업데이트 되지 않는다.
그러나 key값에 index가 아니라 uuid를사용하게 되면 상황이 달라진다.

key값을 uuid로 설정 시 문제점

import { useState } from "react"
import Word from "./02-child"
import {v4 as uuidv4} from "uuid"

export default function MemoizationParentPage(){
	const [data,setData] = useState("철수는 오늘 점심을 맛있게 먹었습니다.")

	const onClickChange = ()=>{
		setData("영희는 오늘 저녁을 맛없게 먹었습니다.")
	}
	return(
		<>
			{data.split("").map((el)=>(
				<Word key={uuidv4} el={el}/>
			))}
			<button>체인지</button>
		</>
	)
}
import { memo } from "react"

export default function Word(props: any){
	console.log("자식이 렌더링 됩니다!",props.el)
	return <span>{props.el}</span>
}

export default memo(Word)

uuid를 사용하면 memo를 걸어놔도 key값이 변경되어 props로 넘어가기 때문에 변경된 부분이 모두 리렌더링 된다.
uuid는 불필요한 리렌더링을 초래하므로 사용하실때는 필요한 상황에서만 주의 해서 사용 해줘야 한다!!

브라우저에 어떻게 그림이 그려질까?

-Critical-Rendering-Path-

  1. 화면을 그려주는데 필요한 리소스(HTML, CSS, JS)를 다운로드 해온다.
  2. HTML과 CSS에서 화면에 렌더해야 할 요소들을 구분 후 렌더되어야 할 HTML, CSS 요소를 합쳐 화면에 그려준다.
  3. 화면에 그려줄 때 해당 요소들이 어느 위치에 놓일지 먼저 그려주는 Layout Reflow와 해당 요소들을 색칠하는 Paint Repaint과정이 발생한다.

⚠️ 렌더트리
렌더트리는 최종적으로 브라우저에 표기될 요소들이라고 생각하면 된다.

위에서 렌더링 시 화면에 렌더해야 하는 요소를 구분하는 작업을 한다고 했다.
이때 HTML의 요소를 구분할 수 있도록 도와주는 것이 DOM(Document Object Model),
CSS요소 를 구분할 수 있도록 도와주는 것이 CSSOM(CSS Object Model)이다.

이렇게 DOM과 CSSOM이 합쳐진것이 바로 렌더트리다.
즉 2번과정과 3번과정이 합쳐진 4번과정의 결과물이 렌더트리라고 볼 수 있다.


Reflow와 Repaint

Reflow란 렌더링 되어야 할 요소들을 화면상 위치를 그려주는 과정!
Repaint는 위치를 잡고 난 이후 색칠을 해주는 과정!

위치를 다시 그리는 건 느림!
왜냐면 색깔도 다시 또 칠해야 하기 때문
따라서 Reflow는 최대한 일어나지 않게하자!

더 오래걸리는 작업은?
=> Reflow!

Prefetch와 Preload

Prefetch

prefetch는 다음페이지를 미리 받아오는 것이며, 현재페이지를 모두 받아온 이후 제일 나중에 다운로드 된다!
따라서 prefetch를 이용하면 페이지가 이동되어도 기다리지 않고 바로 보여줄 수 있다!!

//index.html
<!DOCTYPE html>
<html lang="ko">
  <head>
    <title>프리페치</title>

    <!-- 프리페치: 다음페이지를 미리 다운로드 받으므로, 버튼 클릭시 페이지이동 빠름 -->
    <link rel="prefetch" href="board.html" />
  </head>
  <body>
    <a href="board.html">게시판으로 이동하기</a>
  </body>
</html>

Preload

preload란 현재페이지에서 쓸 이미지들을 모두 다운로드 받아놓는 것이다.
따라서 prefetch와는 다르게 이미지를 css와 js보다 먼저 받아오게 된다!
preload를 이용하면 이미지를 먼저 받아오기 때문에 화면로드 지연을 방지 할 수 있다!!

<!DOCTYPE html>
<html lang="ko">
  <head>
    <title>프리로드</title>

    <!-- 프리로드: 한 번에 6개씩 받아오므로, body태그의 이미지는 가장 마지막에 다운로드 -->
    <!--         눈에 보이는 이미지를 먼저 다운로드 받아서 보여주고, 클릭하면 실행되는 JS는 나중에 받아오기 -->
    <!--         따라서, DOMContentedLoaded 이후, Load까지 완료되는 최종 로드 시간이 더 짧아짐 -->
    <link rel="preload" as="image" href="./dog.jpeg" />

    <!-- 일반로드 -->
    <link rel="stylesheet" href="./index.css" />
    <script src="index1.js"></script>
    <script src="index2.js"></script>
    <script src="index3.js"></script>
    <script src="index4.js"></script>
    <script src="index5.js"></script>
    <script src="index6.js"></script>
  </head>
  <body>
    <img src="./dog.jpeg" />
  </body>
</html>
profile
프론트엔드 대장이 되어보쟈

0개의 댓글