티스토리 뷰

useEffect Hook 사용하기

useEffect 훅은 함수형 컴포넌트에서 쓰이며 기존 클래스 컴포넌트의 라이프사이클 메서드들을 대체한다. 이를 정리하기 위해 간단히 라이프사이클 메서드를 설명하고, useEffect 훅의 효용을 적는다.

 

LifeCycle Method - componentDidMount, componentDidUpdate

  • 생명주기 메서드는 크게 두 종류이다.
    • Will 메서드 : 어떤 작업이 실행되기 직전에 호출
    • Did 메서드 : 어떤 작업이 실행된 직후에 호출
  • 생명주기 메서드에서는 Mount라는 단어가 등장하는데, 여기서 마운트는 리액트가 컴포넌트를 실제 DOM에 삽입한다는 것이다. (리액트는 가상DOM을 사용하니까)
    • 예를 들어 componentDidMount는 컴포넌트가 실제 DOM에 마운팅된 직후에 이 작업을 수행하겠다는 뜻이다.
    • 그 다음 클릭 이벤트 핸들러가 작동하여 스테이트 변경이 일어났다고 하자. 이 때 리액트는 상태 변경을 감지하고 가상 DOM을 실제 DOM에 동기화 하는 업데이트 작업을 수행한다. 업데이트 직후에 어떤 작업을 수행하려면 componentDidUpdate 메서드를 사용하면 된다.
    • 단 componentDidUpdate는 최초 렌더링 시에는 호출되지 않는다.
  • componentDidMount와 componentDidUpdate의 차이를 알아보자.
    • componeneDidMount만 존재하는 경우
    class Example extends Component {
      constructor(props) {
        super(props);
        this.state = {
          count: 0
        };
      }
    
      componentDidMount() {
        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>
        );
      }
    }
    
    
    버튼을 클릭하면 state의 count 값을 1씩 증가시키는 클래스 컴포넌트가 있고, componentDidMount 메서드가 있다. 이 메서드 내에는 도큐먼트 타이틀에 카운트 값을 표시하는 코드가 있다.

    • componentDidUpdate만 존재하는 경우
    class Example extends Component {
      constructor(props) {
        super(props);
        this.state = {
          count: 0
        };
      }
    
      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>
        );
      }
    }
    
    
    이번엔 componentDidUpdate만 존재하는데, 이 경우 최초 렌더링 시 호출되지 않으므로 DOM에 리액트 요소들이 삽입되었을 때에는 document 타이틀이 html에 설정된 타이틀의 상태로 되어 있다.
  • 이 때 버튼을 클릭해서 스테이트를 변경하면 리액트가 이를 update하면서 타이틀도 함께 업데이트된다.
  • 다만 componentDidMount는 최초 렌더링 시 한 번 호출된다. 그렇기 때문에 this.state.count 로 되어있어도, count가 0인 최초의 상태일 때에 한 번만 호출되기 때문에 이후 버튼이 클릭되면서 카운트 값이 업데이트 되어도 타이틀은 변하지 않는다.

useEffect 사용하기 - without Cleanup

  • componentDidMount와 componentDidUpdate는 로직이 분리되어 있다. 그래서 만약 위의 카운터에서, 최초 렌더링 시에도 count를 표시하고 싶고, 이 후 스테이트가 업데이트될 때에도 count를 갱신하고 싶으면 두 메서드를 다 사용해야 한다.코드에서 볼 수 있듯이, 동일한 코드가 중복된다. 즉 최초든 업데이트 이후든 렌더링 될 때마다 수행하고 싶은 작업이 있을 때에 기존의 라이프사이클 메서드를 사용하면 중복이 생긴다.
  •  
  • class Example extends 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> ); } }
  • useEffect 훅은 이를 해결해준다.여기서는 useEffect외에도 함수형 컴포넌트 내에서 스테이트를 가질 수 있게 해주는 useState도 사용했다.
  • useEffect를 사용하면 every render 마다 원하는 작업을 수행할 수 있어 코드를 중복할 필요가 없다.
  • import React, { useState, useEffect } from "react"; const 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> ); }; export default Example;
  • useEffect를 사용하는 방법은 위 코드처럼, 내가 원하는 effect (여기서는 도큐먼트 타이틀을 바꾸는 것) function을 패스해주면 된다. 여기서는 타이틀을 바꾸는 데 사용했지만, 필수적인 API를 불러오거나 data를 fetch할 때 사용할 수도 있을 것이다.
  • useEffect를 사용해서 상태가 변화할 때마다 콜백함수를 re run하게 해서 다음과 같이 textarea가 contents에 따라 자동으로 height 변환이 되도록 할 수도 있다.
    <textarea
            className="form-control input-lg"
            placeholder={placeholder}
            spellCheck="false"
            ref={textareaEl}
            value={contents}
            onChange={e => setContents(e.target.value)}
          />
    
    useState를 사용하여 contents 상태를 업데이트하고, useEffect를 사용해서 리액트는 contents의 변화를 감지한다. contents의 변화가 감지되면 useEffect는 콜백함수를 실행하여 리렌더링하는데, 이 함수의 내용은 textarea의 높이를 자동조절 하는 것이다. textarea의 크기를 조절하는 것은 createRef를 사용해서 DOM 객체에 직접 접근해 변경했다. (textarea의 ref 속성)
  • // textarea 높이 자동 조절 useEffect(() => { textareaEl.current.style.height = "auto"; textareaEl.current.style.height = textareaEl.current.scrollHeight + lineHeight + "px"; }, [contents]);

 

useEffect 사용하기 - with Cleanup

위의 타이틀을 바꾸는 예시는 cleanup이 필요없는 작업이었지만, 어떤 상태를 subscribe하고자 하는 경우에는 cleanup이 필요하다. 안 그러면 memory leak이 생기기 때문이다.

 

  • ComponentDidMount와 ComponentWillUnmount를 사용한 경우
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';
  }
}

친구의 online status를 subscribe하는 ChatAPI가 있다고 할 때에, 이를 cleanup하기 위해 componentWillUnmount가 필요하다. 즉 한 가지 effect를 위해서 두 생명주기 메서드가 중복해서 쓰여야 한다는 것이다.

 

  • useEffect로 이 subscribe와 cleanup 두가지를 모두 처리할 수 있다.
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);
    // Specify how to clean up after this effect:
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

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

cleanup 함수를 useEffect 내에서 리턴하는 방식으로 unsubscribe한다.

 

  • 아래는 useEffect를 사용하여 시간을 계속 업데이트하는 코드이다.
import React, { useState, useEffect } from 'react'; 

const Clock = () => {
  const [time, setTime] = useState(new Date()); 
  
  useEffect(() => {
    const timer = setTimeout(setTime(new Date(), 1000));
    return () => { clearTimeout(timer) };
  }, [time]); 
  
  return (
  	<div>
    	<h2>{time.toLocaleString()}</h2>
    </div>
  )
}

time이라는 변수를 설정하고 초기값은 현재 시간이다. useEffect내에서 subscribe하고 싶은 행동을 설정하는데, 이는 1초가 지나면 time 스테이트를 현재 시간으로 업데이트한다. 그리고 cleanup 메서드를 함수로 리턴한다.

useEffect의 두번째 parameter는 subscribe하고 싶은 것을 배열 안에 담는 것이다. 여기서 time을 subscribe하기 때문에 배열에 담아서 2번째 아규먼트로 전달하고 있다. subscribe하는 것의 상태가 바뀌면 useEffect는 콜백 함수를 재실행한다. 그러므로 1초 뒤에 또 time상태가 업데이트되는 setTimeOut이 반복되는 것이다.

'공부일지(TIL) > JavaScript' 카테고리의 다른 글

ES6 모듈 시스템  (0) 2019.08.09
프로미스의 활용  (0) 2019.08.07
[자바스크립트] this와 함수 Context  (0) 2019.06.22
ES9 features  (0) 2019.04.16
How JS works? (Asynchronous Programming)  (0) 2019.03.19
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/03   »
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
글 보관함