무브라더

[React] Effect Hook 본문

Programming/React

[React] Effect Hook

동스다
반응형
SMALL
import React, { useState, useEffect } from 'react';

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

  // componentDidMount, componentDidUpdate와 같은 방식으로
  useEffect(() => {
    // 브라우저 API를 이용하여 문서 타이틀을 업데이트
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Effect Hook을 사용하면 함수 컴포넌트에서 side effect 를 수행할 수 있다.

 

데이터 가져오기, 구독설정, 수동으로 React 컴포넌트의 DOM을 수정하는 것까지 이 모든 것이 side effects 이다.

side effect 에 대해 익숙하지 않을 수 있지만 클래스 라이프사이클에 대해서 알고 있다면 useEffect Hook을

componentDidMount 와 componentDidUpdate, componentWillUnmount가 합쳐진 것으로 생각해도 좋다.

 

React 컴포넌트에는 일반적으로 두 종류의 side effects가 있다. clean-up이 필요한 것과 그렇지 않은 것 , 이 둘을 어떻게 구분해야할지 자세하게 알아보자

 

Clean-up을 이용하지 않는 Effects

React가 DOM을 업데이트 한 뒤 추가로 코드를 실행해야 하는 경우가 있다.

네트워크 리퀘스트 , DOM 수동 조작, 로깅 등은 clean-up이 필요 없는 경우들이다. 이러한 예들은 실행 이후 신경 쓸 것이 없기 때문이다. class 와 hook이 이러한 side effects를 어떻게 다르게 구현하는지 비교해보자.

 

Class를 사용하는 예시

React의 class 컴포너트에서 render 메소드 그 자체는 side effect를 발생시키지 않는다. 시기적으로 effect를 수행하는 것은 React가 DOM을 업데이트 하고 난 이후다.

React class 에서 side effect를 componentDidMount와 componentDidUpdate 에 두는 것이 바로 이러한 이유 때문이다.

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
  }
  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

위 코드에서 주의할 점은 class 안의 두개의 생명주기 메소드에 같은 코드가 중복되면 안되는 것이다.

왜냐하면 컴포넌트에 필요로 하는 기능에서는 마운트된 단계이던가 업데이트되던가하는 생명주기에 관계없이 같은 side effect를 수행해야하기 때문이다. 개념적으로는 렌더링 이후에는 항상 같은 코드가 수행되는 것인데 리액트 클래스 컴포넌트에서는 해당메소드가 존재 하지 않는다. 함수를 별개의 메소드로 뽑아낸다고 해도 여전히 두 장소에서 함수를 불러내야한다.

 

Hook을 사용하는 예시

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

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

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

- useEffect가 하는일

useEffect Hook을 이용하여 React에게 컴포넌트가 렌더링 이후에 어떤 일을 수행해야하는지 전달해준다.

React는 우리가 넘긴 함수를 기억했다가 ( 이 함수는 effect ) DOM 업데이트를 수행한 이후에 불러낼 것이다.

위의 경우에는 effect를 통해 문서 타이틀을 지정하지만, 이 외에도 데이터를 가져오거나 다른 명령형 API를 불러내는 일도 할 수 있다.

 

- useEffect를 컴포넌트 안에서 불러내는 이유

useEffect 를 컴포넌트 내부에 둠으로써 effect 를 통해 count state변수에 접근할 수 있게 된다.

함수 범위 안에 존재하기 때문에 특별한 API 없이도 값을 얻을 수 있다.

Hook은 자바스크립트의 클로저를 이용하여 React에 한정된 API를 고안하는 것 보다 자바스크립트가 이미 가지고 있는 방법을 이용하여 문제를 해결한다.

 

- useEffect는 렌더링 이후에 매번 수행이 되는지

useEffect는 기본적으로 첫번째 렌더링과 이후의 모든 업데이트에서 수행된다.

마운팅과 업데이트라는 방식으로 생각하는 대신 effect를 렌더링 이후에 발생하는 것으로 생각하면 된다.

React는 effect가 수행되는 시점에 이미 DOM이 업데이트 되었음을 보장한다.

 

 

Clean-up을 이용하는 Effects

clean-up이 필요없는 effect도 있지만 clean-up이 필요한 effect도 있다.

예를 들어 외부 데이터에 구독을 설정해야하는 경우를 생각해보면 메모리 누수가 발생하지 않도록 clean-up 하는 것이 매우 중요하다.

 

Class를 사용하는 예시

React class 에서는 흔히 componentDidMount에 구독을 설정한 뒤 componentWillUnmount 에서 이를 clean-up 한다 

아래 코드는 친구의 온라인 상태를 구독할 수 있는 ChatAPI 모듈의 예를 들어보았다. class를 이용하여 상태를 구독하고 보여주는 코드이다.

class FriendStatus extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOnline: null };
    this.handleStatusChange = this.handleStatusChange.bind(this);
  }

  componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }
  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }
  handleStatusChange(status) {
    this.setState({
      isOnline: status.isOnline
    });
  }

  render() {
    if (this.state.isOnline === null) {
      return 'Loading...';
    }
    return this.state.isOnline ? 'Online' : 'Offline';
  }
}

 

 

componentDidMount 와 componentWillUnmount 를 살펴보면 두 개의 메소드 내에 개념상 똑같은 effect에 대한 코드가 있음에도 불구하고 생명주기 메소드는 이를 분리하게 만든다.

 

Hook을 사용하는 예시

clean-up 의 실행을 위해 별개의 effect가 필요하다고 생각할 수도 있다. 하지만 구독의 추가와 제거를 위한 코드는

결합도(연관성)이 높기 때문에 useEffect는 이를 함께 다루도록 고안되어있다.

effect 함수를 반환하면 React는 그 함수를 정리가 필요한 때 실행시킬 것이다.

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

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // effect 이후에 어떻게 정리(clean-up)할 것인지 표시
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

- effect 에서 함수를 반환하는 이유

이는 effect를 위한 추가적인 clean-up 메커니즘이다. 모든 effect는 정리를 위한 함수를 반환할 수 있다.

이러한 점이 구독의 추가와 제거를 위한 로직을 묶어둘 수 있게 한다.

구독의 추가와 제거가 모두 하나의 effect를 구성하는 것이다.

 

- React가 effect를 clean-up하는 시점

React는 컴포넌트가 마운트 해제되는 때에 clean-up을 실행한다.

하지만 위의 예시에서 보았듯이 effect는 한번이 아니라 렌더링이 실행되는 때마다 실행된다.

React가 다음 차례의 effect를 실행하기 전에 이전의 렌더링에서 파생된 effect 또한 정리하는 이유가 바로 이 때문이다.

 

요약

useEffect가 컴포넌트의 렌더링 이후에 다양한 side effects를 표현할 수 있음을 알아봤다

effect에 clean-up이 필요한 경우에는 함수를 반환한다.

useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

clean-up이 필요없는 경우에는 어떤 것도 반환하지 않는다.

useEffect(() => {
    document.title = `You clicked ${count} times`;
});

 

출처

https://ko.reactjs.org/docs/hooks-effect.html#explanation-why-effects-run-on-each-update

 

Using the Effect Hook – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

반응형
LIST
Comments