-
[20230317] 리액트 성능최적화 Hook (useMemo, useCallback)TIL 2023. 3. 17. 05:41
리액트 렌더링에 대해 다시 공부해보며 성능최적화 Hook의 개념을 공부해보았다.
먼저 리액트에서 렌더링되는 경우를 다시 짚어보면 아래와 같이 세가지의 경우가 있다.
1. 부모 컴포넌트가 렌더링된 경우
2. 컴포넌트의 state가 변경된 경우
3. 부모로부터 전달받은 props값이 변경된 경우
memo라는 함수는 컴포넌트에 변경된 사항이 없을 경우 불필요한 리렌더링을 막는 함수이다. 그런데 이러한 memo를 사용하더라도 아래와 같이 props를 넘겨주는 컴포넌트가 있다면 그 props가 리렌더링됨에 따라 재생성되므로 어쩔 수 없이 리렌더링되는 문제가 있다. 그래서 props로 함수를 넘겨주는 경우에는 useCallback을, 객체나 배열을 넘겨주는 경우에는 useMemo를 사용하여 의존성 배열안의 값이 바뀔 때만 재생성하도록 할 수 있다.
참고로 let으로 선언된 변수는 값이 바뀌어도 어차피 리렌더링이 되지 않으므로, 이러한 Hook을 쓰는 것이 의미가 없다.
// src/App.jsx import React, { useCallback, useMemo, useState } from "react"; import Button from "./components/Button"; import List from "./components/List"; const App = () => { // 이 state가 바뀌면 App.js가 리렌더링 된다. const [value, setValue] = useState(""); // App.js가 리렌더링 될때마다 재생성됨 const onChangeHandler = (e) => { setValue(e.target.value); }; // App.js가 리렌더링 될때마다 재생성됨 // 의존성 배열안의 값이 바뀔때만 해당 함수가 재생성된다. const onClickHandler = useCallback(() => { console.log("hello button!"); }, []); // useCallback의 사용대상은 함수, useMemo의 사용대상은 객체나 배열과 같은 값이다. const data = useMemo(() => { return [ { id: 1, title: "react", }, ]; }, []); return ( <div> <input type="text" value={value} onChange={onChangeHandler} /> {/* 매번 재생성되는 함수를 props로 넘겨줌 -> Button.js 리렌더링 유발 */} <Button onClick={onClickHandler} /> {/* props로 받은 배열이 App.js가 리렌더링될 때마다 재생성되므로 List가 memo를 사용해도 재생성 */} <List data={data} /> </div> ); }; export default App;
위와 같이 Button과 List 컴포넌트에 props로 넘겨주는 함수와 배열을 useCallback과 useMemo를 사용했기 때문에 각각의 컴포넌트(Button과 List)가 재렌더링되지 않는 것을 알 수 있다.
Button과 List 컴포넌트는 console.log를 통해 재렌더링이 되는 것인지 파악할 수 있었다.
// components/Button.js import React from "react"; import { memo } from "react"; // props를 넘겨주지 않는 경우에는 memo만 사용해도 리렌더링이 일어나지 않는다. const Button = ({ onClick }) => { console.log("Button : 리렌더링되고 있어요."); return <button onClick={onClick}>버튼</button>; }; export default memo(Button);
// src/components/List.jsx import React, { memo } from "react"; const List = ({ data }) => { console.log("List : 리렌더링되고 있어요."); return ( <div> {data.map((todo) => ( <div key={todo.id}>{todo.title}</div> ))} </div> ); }; export default memo(List);
그런데 useCallback이나 useMemo의 무분별한 사용은 오히려 퍼포먼스 성능에 악영향을 미칠 수 있다고 한다. 반드시 리렌더링이 필요한 component나 값에는 이 최적화 Hook을 사용하는 것이 오히려 좋지가 않다. 의도치 않게 업데이트가 되지 않는 상황이 있을 수 있기 때문이다. 또한 이러한 Hook들을 사용시 리액트에게 렌더링 이후의 값과 이전의 값을 비교해서 같으면 재생성하고 같지 않으면 재생성하지 않도록 주문을 한다. 즉 전과 후의 값을 비교하는 과정이 추가가 되므로 이런 Hook들의 무분별한 남용은 불필요한 비교과정을 추가하므로 퍼포먼스 성능에 악영항이 될 수 있다.
- useMemo
- 렌더링 될 때마다 momorization 기법을 적용하여 저장 후 값을 그대로 사용한다.
- 두번째 인자인 deps 배열 안의 값이 바뀌면 함수를 호출해서 연산하고, 값이 바뀌지 않으면 이전에 연산한 값을 재사용한다.
- useCallback
- useMemo를 기반으로 만들어졌기 때문에 비슷한 점을 가졌다.
- useMemo는 특정 결과값을 재사용할 때 쓰고, useCallback은 특정 함수를 재사용할 때 사용한다.
- useCallback을 사용하면 해당 컴포넌트가 렌더링되더라도 그 함수가 의존하는 값들이 바뀌지 않는 한 기존 함수를 계속해서 반환한다.
'TIL' 카테고리의 다른 글
[230106] 게시판 구조 설계 (0) 2023.01.07 [221219] 좋아요 기능 구현 요구 사항 (0) 2022.12.19 [221207] setState의 함수형 업데이트방식 / URI vs URL vs URN / query parameter, query string, path variable (0) 2022.12.07 [221207] css hover와 비동기처리 (1) 2022.12.07 [221206] Todo 리스트 페이지를 리덕스와 라우터 사용하여 만들기 (0) 2022.12.06 - useMemo