useCallback한 함수를 다시 useCallback으로 깜싸는이유
본문 바로가기

Frontend/React

useCallback한 함수를 다시 useCallback으로 깜싸는이유

useCallback을 하는이유

useCallback을 사용하지않을때 APP컴포넌트 랜더링발생

첨부한 움짤은 증가 버튼을 누르기만하였는데 App컴포넌트 자체가 랜더링되버리는 예시이다. (파란색 선이 App컴포넌트를 의미함) 만약에 자식 컴포넌트들이 여러개있고 이들이 모두 increment함수를 자식 컴포넌트에 전달하게된다면 App컴포넌트뿐만 아니라 자식 컴포넌트들까지도 불필요하게 랜더링이 되버린다. 이를 해결하는 방법중의 하나가 useCallback이다.

 

useCallback이란

함수를 자식컴포넌트에 전달할때 불필요한 렌더링이 발생하지않게하는 함수를 메모이제이션하는 최적화 방법으로

메모이제이션은 쉽게말해서 함수를 캐시한다고생각하면된다.

아래는 useCallback 예시이다.

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

function App() {
  const [count, setCount] = useState(0);

  // 이미 useCallback으로 래핑된 함수
  const increment = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  // increment 함수를 useCallback으로 래핑
  const incrementAgain = useCallback(increment, [increment]);

  return (
    <div>
      <p>카운트: {count}</p>
      <button onClick={incrementAgain}>증가</button>
    </div>
  );
}

export default App;

 

왜 함수를 useCallback을 감싸고 또 감싸지?

useCallback으로 감싸진 increment함수의 의존성 배열에 count가 있기때문에

count값이 변경될때마다 increment함수가 새로생성될것이며 이로인하여 incrementAgain함수가 불필요하게 재생성된다. 따라서 한번더 useCallback으로 감싸면 increment함수가 변경될때만 incrementAgain함수가 새로 생성되고 count값이 변경될때는 불필요한 재생성을 방지할수있다.

 

useCallback 한개만 쓰면안되나?

강의에서 배운내용을 토대로 의존성배열을 빈배열로 두고 setCount에서 이전 count를 파라미터로 넣어줘도 결과는 같다.

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

function App() {
  const [count, setCount] = useState(0);

  const increment = useCallback(() => {
    setCount((count)=>count + 1);
  },[])

  return (
    <div>
      <p>카운트: {count}</p>
      <button onClick={increment}>증가</button>
    </div>
  );
}

export default App;

 

위의 2개의 예시의 차이는?

두개의 결과는 불필요한 렌더링과 함수호출을 방지하고있다는 점에서 같지만 가독성과 유지보수성을 고려하여 코드를 작성할때는

incrementAgain과 같은 중간 변수나 함수를 사용하는것이 좋다고한다.

 

실제활용예시

메타마스크 문서에서 가져왔다. 사실은 이게 한번에 이해가안됬어서 공부하게됨.

  const [wallet, setWallet] = useState(disconnectedState)
  // useCallback ensures that you don't uselessly recreate the _updateWallet function on every render
  const _updateWallet = useCallback(async (providedAccounts?: any) => {
    const accounts = providedAccounts || await window.ethereum.request(
      { method: 'eth_accounts' },
    )

    if (accounts.length === 0) {
      // If there are no accounts, then the user is disconnected
      setWallet(disconnectedState)
      return
    }

    const balance = formatBalance(await window.ethereum.request({
      method: 'eth_getBalance',
      params: [accounts[0], 'latest'],
    }))
    const chainId = await window.ethereum.request({
      method: 'eth_chainId',
    })

    setWallet({ accounts, balance, chainId })
    
  }, [])

  const updateWalletAndAccounts = useCallback(_updateWallet, [_updateWallet])
  const updateWallet = useCallback((accounts: any) => _updateWallet(accounts), [_updateWallet])

 

반응형