useCallback

최근 추가된 hooks의 useCallback 함수에 대해 찾아 보았다.
이는 성능 향상을 위해 불필요한 렌더링을 방지하려는 경우 적합하다. 자식 컴포넌트에 콜백을 전달하는 두 가지 방법을 비교해 보자.

1. class + inline arrow function

class Foo extends Component {
  handleClick() {
    console.log('Click happened');
  }
  render() {
    return <Button onClick={() => this.handleClick()}>Click Me</Button>;
  }
}

https://codesandbox.io/s/21yqlr2mny

로그를 보면 <Button> 컴포넌트가 PureComponent 임에도 불구하고

  • 1번 예제는 Foo 가 re-render 될 때마다 Button 이 매번 re-render 된다. Foo 가 매번 render() 할 때마다 새로운 함수를 만들기 때문이다.

2. class + constructor

class Foo extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    console.log('Click happened');
  }
  render() {
    return <Button onClick={this.handleClick}>Click Me</Button>;
  }
}

https://codesandbox.io/s/ko48v8y5pv

  • 2번 예제는 handleClick 함수가 생성자에서 단 한번만 만들어 지고 렌더에서 재사용 된다.
  • handleClick = () => {}; 같은 방식도 괜찮음

위의 두 예제를 hook으로 만들면 아래와 같다.

1. functional component + arrow function

function Foo() {
  const handleClick = () => {
    console.log('Click happened');
  }
  return <Button onClick={handleClick}>Click Me</Button>;
}

https://codesandbox.io/s/z2vl88961p

  • 클래스의 1번째 예제와 마찬가지로 Button이 항상 re-render 된다.

2. functional component + useCallback

function Foo() {
  const memoizedHandleClick = useCallback(
    () => console.log('Click happened'), [],
  );
  return <Button onClick={memoizedHandleClick}>Click Me</Button>;
}

https://codesandbox.io/s/o73175ywwq

  • useCallback을 이용하고 2번째 인자에 빈 배열을 넣어 주면 함수가 최초 1회만 생성 된다.
  • 만약 어떤 값에 따라 함수가 갱신 되어야 하면 두번째 인자의 배열에 원하는 값을 넣으면 된다.

React 문서에서는 대부분의 경우 첫번째 방법을 사용하는 것이 좋고 일반적으로 괜찮다고 한다. 하지만 성능에 문제가 있는 경우 모든 것을 최적화 하라고 합니다.

아래의 예제는 두번째 인자를 의도적으로 전달하지 않음으로 써 문제 상황을 만든 것입니다. 확인 버튼을 클릭해 봐도 함수가 변경되지 않아 초기의 count값을 참조하고 있다.

https://codesandbox.io/s/x8lmxyywz