react

useCallback을 케이크처럼 쉽게 먹는 방법

냠냠맨 2023. 3. 17. 12:40

🐕 useCallback을 쓰는 시점

useMemo와 비슷하게도 메모이제이션을 기반으로 한 훅입니다.

캐싱해둔 값을 그대로 사용하는 건데...

useCallback은 콜백함수 그 자체를 메모이제이션 해줍니다.

useMemo가 return 값을 메모이제이션 해주는 것과는 살짝 대비가 되는 느낌

컴포넌트가 다시 렌더링 되더라도 함수가 초기화되는걸 막을 수 있다.

이후에 렌더링이 될 때는 이전에 이미 만들어둔 함수 객체를 계속 유지하면서 사용할 수 있다.

 

이걸 왜 써야할까요?

1. 함수 컴포넌트는 상태변화시 다시 렌더링된다.

2. 렌더링 될 때 함수 내부 변수는 초기화된다.

3. 따라서 렌더링 전 변수와 렌더링 후 변수는 메모리 주솟값이 다르다.

4. useEffect 등에서 사용하는 의존성 배열은 주솟값이 다른 객체를 다른 객체로 인식하여 렌더링이 계속 일어난다.

5. 따라서 useCallback을 이용해 함수 자체를 Memoization 해주어야 한다.


👻useCallback의 기본적인 사용법

useCallback(function , dependency)

useMemo와 구조자체는 동일합니다.

대신 인자로 들어간 함수의 리턴값을 기억해주는 게 아니라

콜백 함수 그 자체를 기억 한다는 점


🥶 의존성 배열이 어떻게 되냐면..

import React, { useState, useCallback, useEffect, useMemo } from 'react';
import { Fragment } from 'react';

const App = () => {
  const [number, setNumber] = useState(0);
  const [toggle, setToggle] = useState(true);
  const someFunction = () => {
    console.log('someFunc :', number);
  };
  useEffect(() => {
    console.log('someFunction이 변경되었습니다.');
  }, [someFunction]);

  return (
    <>
      <input
        type="number"
        value={number}
        onChange={(e) => setNumber(e.target.value)}
      />

      <button onClick={() => setToggle(!toggle)}>{toggle.toString()}</button>
      <br />
      <button onClick={someFunction}>Call someFunc</button>
    </>
  );
};

export default App;

이렇게 someFunction이 변경될때마다 useEffect가 호출되는 코드를 작성했습니다.

놀랍게도.. someFunction을 건드리지않고 number 스테이트를 변경시켜서 리렌더링이 일어나도

useEffect가 실행되면서 콘솔창에

'someFunction이 변경되었습니다'가 출력되는 것을 확인할 수 있습니다.

 

앞서 살펴보았듯이 함수도 객체이고 함수가 호출되면 기존 값을 재활용하는게아니라

아예 새로운 주소에 값을 할당해주기 때문에 일어나는 이슈입니다.

let arr = []
let arr2 = []

console.log(arr === arr2) // false

마치 이것과 같은 이유인거죠!!

이걸 해결해주기 위해서 우리는 someFunction을 어딘가에 저장해둘 필요성이 있을 것입니다.

따라서 useCallback을 고려할 수 있습니다.


🌞useCallback을 사용해 문제를 해결하면?

import React, { useState, useCallback, useEffect, useMemo } from 'react';
import { Fragment } from 'react';

const App = () => {
  const [number, setNumber] = useState(0);
  const [toggle, setToggle] = useState(true);

  const someFunction = useCallback(() => {
    console.log(`someFunc : number: ${number}`);
    return;
  }, [number]);


  useEffect(() => {
    console.log('someFunction이 변경되었습니다.');
  }, [someFunction]);

  return (
    <>
      <input
        type="number"
        value={number}
        onChange={(e) => setNumber(e.target.value)}
      />

      <button onClick={() => setToggle(!toggle)}>{toggle.toString()}</button>
      <br />
      <button onClick={someFunction}>Call someFunc</button>
    </>
  );
};

export default App;

useCallback안에 함수를 감싸줬습니다.

이렇게하면 useCallback으로 인해 우리가 의도한대로

useEffect는 새로고침을 해줄 때나 실행됩니다.


😋useCallback 응용

이렇게 Change Theme가  변경되었을 때

함수는 새로 만들어지지 않게 하고 싶어서 useCallback으로 감싸주는 형태의 코드를 짜겠습니다. 


🤢App.js

import React, { useState, useCallback, useEffect, useMemo } from 'react';
import { Fragment } from 'react';
import Box from './Box';

const App = () => {
  const [size, setSizes] = useState(100);
  const [isDark, setIsDark] = useState(false);
  
  const createBoxStyle = useCallback(() => {
    console.log('createBOX 호출');
    return {
      backgroundColor: 'pink',
      width: `${size}px`,
      height: `${size}px`,
    };
  }, [size]);

  return (
    <>
      <div style={{ background: isDark ? 'black' : 'white' }}>
        <input
          type="number"
          value={size}
          onChange={(e) => setSizes(e.target.value)}
        />
        <button onClick={() => setIsDark(!isDark)}> Change Theme</button>
        <Box createBoxStyle={createBoxStyle} />
      </div>
    </>
  );
};

export default App;

App.js는 다음과 같습니다.

Box.js

import React, { useEffect, useState } from 'react';

const Box = ({ createBoxStyle }) => {
  const [style, setStyle] = useState({});

  useEffect(() => {
    console.log('박스키우기');
    setStyle(createBoxStyle());
  }, [createBoxStyle]);

  return <div style={style}></div>;
};

export default Box;

 

 

 


이해했다고 생각할 때가 가장 무서울 때다.

 

반응형