본문 바로가기
코딩이야기/React 공부

[React] React-Hooks 간단 정리 (2)

by TaeHyeon0412 2024. 7. 3.

대표적인 최적화 React-Hooks

리-렌더링의 발생 조건

  1. 컴포넌트에서 state가 바뀌었을 때
  2. 컴포넌트가 내려받은 props가 변경되었을 때
  3. 부모 컴포넌트가 리-렌더링 된 경우 자식 컴포넌트는 모두

리액트에서 불필요한 렌더링이 발생하지 않도록 최적화하는 대표적인 방법

  • memo(React.memo) : 컴포넌트를 메모이제이션(memoization)
  • useCallback : 함수를 메모이제이션(memoization)
  • useMemo : 함수가 리턴하는 값을 메모이제이션(memoization)

 


 

•React.memo

부모가 리렌더링 되면 자식은 모두 리렌더링 됩니다.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
//App.jsx
import React, { useState } from "react";
import Box1 from "./components/Box1";
import Box2 from "./components/Box2";
 
const boxesStyle = {
  display: "flex",
  marginTop: "10px",
};
 
function App() {
  console.log("App 컴포넌트가 렌더링되었습니다!");
 
  const [count, setCount] = useState(0);
 
  // 1을 증가시키는 함수
  const onPlusButtonClickHandler = () => {
    setCount(count + 1);
  };
 
  // 1을 감소시키는 함수
  const onMinusButtonClickHandler = () => {
    setCount(count - 1);
  };
 
  return (
    <>
      <h3>카운트 예제입니다!</h3>
      <p>현재 카운트 : {count}</p>
      <button onClick={onPlusButtonClickHandler}>+</button>
      <button onClick={onMinusButtonClickHandler}>-</button>
      <div style={boxesStyle}>
        <Box1 />
        <Box2 />
      </div>
    </>
  );
}
 
export default App;

 
//Box1.jsx
import React from "react";
 
const boxStyle = {
  width: "100px",
  height: "100px",
  backgroundColor: "#91c49f",
  color: "white",
 
  // 가운데 정렬 3종세트
  display: "flex",
  justifyContent: "center",
  alignItems: "center",
};
 
function Box1() {
  console.log("Box1이 렌더링되었습니다.");
  return <div style={boxStyle}>Box1</div>;
}
 
export default Box1;
 
//Box2 동일
cs

App.jsx의 버튼을 누르면 자식인 Box1,2까지 전부 리렌더링 되는데
이것을 memo를 통해 해결할 수 있습니다.

1
2
export default React.memo(Box1);
export default React.memo(Box2);
cs

이렇게 React.memo를 컴포넌트 전체를 감싸주면 컴포넌트를 메모이제이션(memoization)할 수 있습니다.

 


•useCallback

React.memo는 컴포넌트를 메모이제이션 했다면,
useCallback은 인자로 들어오는 함수 자체를 메모이제이션(memoization) 합니다.

아래의 코드는 React.memo를 한 코드에 초기화 함수를 추가해 주었습니다.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//App.jsx
 
function App() {
  console.log("App 컴포넌트가 렌더링되었습니다!");
 
  const [count, setCount] = useState(0);
 
  // 1을 증가시키는 함수
  const onPlusButtonClickHandler = () => {
    setCount(count + 1);
  };
 
  // 1을 감소시키는 함수
  const onMinusButtonClickHandler = () => {
    setCount(count - 1);
  };
 
  //카운트 초기화 함수 추가
   const initCount = ( ) => {
    setCount(0);
  };
 
  return (
    <>
      <h3>카운트 예제입니다!</h3>
      <p>현재 카운트 : {count}</p>
      <button onClick={onPlusButtonClickHandler}>+</button>
      <button onClick={onMinusButtonClickHandler}>-</button>
      <div style={boxesStyle}>
        <Box1 initCount={initCount}/> //props로 함수를 내려줌
        <Box2 />
      </div>
    </>
  );
}
 
//Box1.jsx
 
...
 
function Box1({ initCount }) {
  console.log("Box1이 렌더링되었습니다.");


  return (
    <div style={boxStyle}>
      <button onClick={()=>{initCount()}}>초기화</button>
    </div>
  );
}
 
...
cs

이렇게 하면 App, Box1 둘다 리렌더링 됩니다.
React.memo를 통해 메모이제이션을 했는데 리렌더링이 되는 이유❔
✔함수형 컴포넌트를 사용하기 때문에 App.jsx가 리렌더링 되면서 코드가 다시 만들어지기 때문입니다.

1
2
3
4
//카운트 초기화 함수 추가 << App.jsx에 있는 요녀석
   const initCount = ( ) => {
    setCount(0);
  };
cs

이것을 useCallback을 이용해 해결할 수 있습니다.

1
2
3
4
5
const initCount = useCallback(() => {
  setCount(0);
}, [ ]); 
 
//useCallback을 쓰고 마지막에 의존성배열 [ ]을 추가하여 props를 보내도 변하지 않게 해준다.
cs

 


•useMemo

useMemo는 함수가 리턴하는 값을 메모이제이션(memoization)하는 것으로 

1
2
3
4
5
6
7
// as-is
const value = 반환할_함수();
 
// to-be
const value = useMemo(()=> {
    return 반환할_함수()
}, [dependencyArray]);
cs

dependency Array의 값이 변경 될 때만 반환할 함수( )가 호출되고
그 외의 경우에는 memoization 해놨던 값을 가져오기만 합니다.

 

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//App.jsx
 
import "./App.css";
import HeavyComponent from "./components/HeavyComponent";
 
function App() {
  const navStyleObj = {
    backgroundColor: "yellow",
    marginBottom: "30px",
  };
 
  const footerStyleObj = {
    backgroundColor: "green",
    marginTop: "30px",
  };
 
  return (
    <>
      <nav style={navStyleObj}>네비게이션 바</nav>
      <HeavyComponent />
      <footer style={footerStyleObj}>푸터 영역이에요</footer>
    </>
  );
}
 
export default App;
 
 
//components>HeavyComponent.jsx
 
import React, { useState, useMemo } from "react";
 
function HeavyButton() {
  const [count, setCount] = useState(0);
 
  const heavyWork = () => {
    for (let i = 0; i < 1000000000; i++) {}
    return 100;
  };
 
    // CASE 1 : useMemo를 사용하지 않았을 때
  const value = heavyWork();
 
    // CASE 2 : useMemo를 사용했을 때
  // const value = useMemo(() => heavyWork(), []);
 
  return (
    <>
      <p>나는 {value}을 가져오는 엄청 무거운 작업을 하는 컴포넌트야!</p>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        누르면 아래 count가 올라가요!
      </button>
      <br />
      {count}
    </>
  );
}
 
export default HeavyButton;
cs


HeavyComponent 안에서는
const value = heavyWork() 를 통해서 value값을 세팅해주고 있고
다른 state(카운트)가 바뀔 때 마다 계속해서 호출되는데 useMemo( )로 감싸주게 되면 처음 리턴한 값을 저장하기 때문에 state가 바뀌어도 변경되지 않습니다.

1
2
3
4
5
// CASE 1 : useMemo를 사용하지 않았을 때
  const value = heavyWork();
 
    // CASE 2 : useMemo를 사용했을 때
const value = useMemo(() => heavyWork(), []);
cs