본문 바로가기
TIL/엘리스 SW 엔지니어 트랙 2기

리액트(React) Effect Hook 엘리스 코딩 TIL (20220630)

by VANAV 2022. 7. 4.

"Effect Hook의 사용과 이해" 리액트(React) 엘리스 코딩 daliy TIL 

 

안녕하세요! 오늘은 엘리스 코딩 리액트 2022/06/30에 "Effect hook"의 사용과 이해에 대해 알아보도록 하겠습니다.

우선 effect hook에 대해 살펴본 후 effect hook과 관련한 Clean up과 같은 연관성이 있는 요소들에 대한 내용을 파악해보고 관련 내용을 최종 요약 정리를 해볼 예정입니다.

Effect Hook

 

[1] Effect Hook 이해하기

- Effect Hook의 사용

React의 컴포넌트는 다음과 같은 생명주기를 가지는데요!

a. Mounting(컴포넌트 생성)

b. Updating(컴포넌트 수정)

c. Unmounting(컴포넌트 해제) 

출처 : [엘리스 : 올인원 디지털 교육 플랫폼] 엘리스 SW  엔지니어 트랙 2기 학습 자료에 첨부된 이미지를 발췌

 

여기서 Effect Hook을 사용하면 함수 컴포넌트에서 side effect(side effect = 렌더링 된 이후에 비동기로 처리되어야 하는 부수적인 효과들을 말함)를 사용할 수 있습니다. 

 

Effect Hook는 componentDidMount, componentDidUpdate, componentWillUnmount가 합쳐진 것으로 생각해도 좋습니다. (이때 componentDidMount, componentDidUpdate, componentWillUnmount는 클래스형 컴포넌트의 생명주기 메소드입니다.)

 

해당 메소드들은 위의 그림에서 알 수 있듯 Mounting, Updating, Unmounting가 각각 이후 실행되는 메소드입니다.

 

- 정리(Clean-up)

Effect Hook을 직접 사용하기 전 정리(Clean-up)에 대해 알아보겠습니다.

React 컴포넌트에는 두 종류의 side effects가 존재합니다.

a. 정리(Clean-up)가 필요한 것

b. 정리(Clean-up)이 필요하지 않은 것

 

 

이때 정리란, 클래스형 컴포넌트에서 Unmounting시 componentWillUnmount를 이용해서 더 이상 사용하지 않는 컴포넌트를 해제하는 것을 말합니다.

(ex> 메모리를 많이 사용하는 컴포넌트는 메모리 누수를 막기 위해 사용하지 않는 경우 componentWillUnmount 메소드에서 메모리를 해제하는 설정을 해줘야 합니다.)

 

그런데 <정리가 필요하지 않은 side effects 같은 경우>
즉, React가 DOM을 업데이트한 뒤 추가로 코드를 실행해야 하는 경우엔 side effects 정리가 필요 없습니다.

(= 컴포넌트 실행 이후 신경 쓸 것이 없는 경우를 뜻합니다.)

// 코드블럭 1

// 버튼 클릭 시 1씩 카운트를 추가하는 컴포넌트.
// 해당 컴포넌트는 렌더링 이후 계속 사용되기 때문에 따로 정리가 필요하지 않다.

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>
    );
  }
}

반면 <정리를 이용하는 side effects>
즉, React가 DOM을 업데이트한 뒤 추가로 코드를 실행할 필요가 없으면 정리를 해줘야 합니다.

(만약 그렇지 않으면 경우에 따라 메모리 누수가 발생해 시스템에 치명적인 영향을 미칠 수 있기 때문입니다.)

// 코드블럭 2

// 채팅 앱에서 친구의 상태를 보여주는 컴포넌트입니다.

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';
  }
}

(ChatAPI 모듈, subscribeToFriendStatus, unsubscribeFromFriendStatus 함수 = 일단 예시(자세한 의미 파악은 다음에!))

간단히 설명하자면 해당 컴포넌트는 Mounting 시 subscribeToFriendStatus를 통해 접속 중인 친구 정보를 가져옵니다.

그리고 Unmounting unsubscribeFromFriendStatus를 통해 접속 중인 친구 정보를 더 이상 가져오지 않는 것입니다.

(다만 해당 예제가 완전하기 위해서는 친구의 상태가 변하는 것을 감지하기 위해 Updating에 대한 componentDidUpdate도 필요합니다.)

 

만약 정리하지 않으면, 계속해서 컴포넌트가 친구 정보를 가져오기 때문에 메모리 누수가 발생하는 것입니다.

Q. 그래서 Effect Hook은 왜 사용하나요?

 

혹시 정리에 대한 예제 코드들의 문제점이 보이시나요?

바로 각 생명주기 메소드(componentDidMount, componentDidUpdate, componentDidUnmount)에 똑같은 코드들이 들어 있는 것인데요. Effect Hook을 이용하면 이렇게 중복되는 코드를 함수형 컴포넌트에서 간단하게 관리할 수 있기 때문에 사용하는 것 입니다.

 

 

[2] Effect Hook 사용하기

앞서 구현한 코드블럭 1(정리가 필요 없는 클래스형 컴포넌트), 코드블럭 2(정리가 필요한 클래스형 컴포넌트) 의 내용을 Effect Hook을 이용한 함수형 컴포넌트로 구현해보겠습니다.

 

 

- 정리가 필요 없는 함수형 컴포넌트

먼저 Effect Hook을 구현하기 위해서는 useEffect를 import 해줘야 합니다.

그리고 useEffect() 메소드와 화살표 함수를 이용해 아래 코드처럼 작성하면 됩니다.

import React, { useEffect } from 'react';

useEffect(() => {
    실행할 코드;
});

 

useEffect는 우리가 작성한 함수(이를 “effect”라고 합니다)를 기억하고 있다가 렌더링 이후 DOM 업데이트 시마다 실행합니다. useEffect() 메소드는 클래스형 컴포넌트에 있는 두 생명주기 메소드(componentDidMount, componentDidUpdate)와 동일한 기능을 한다고 이해하시면 됩니다.

componentDidMount() {
    실행할 코드;
}
componentDidUpdate() {
    실행할 코드;
}

 

정리하자면 생명주기 메소드를 함수형 컴포넌트에서 사용하기 위해 useEffect를 이용하는 것입니다!

 

// 코드블럭 3
// App.js 파일의 코드

import "./App.css";

// useEffect를 import 하세요.
import React, { useState, useEffect } from "react";

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

  // useEffect()와 화살표 함수를 이용해 버튼이 클릭된 횟수를 경고창으로 띄우세요.
useEffect(() => {
    alert(`${count}번 클릭했습니다.`)
});
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>버튼 클릭</button>
    </div>
  );
}

export default App;

Tips !

  • import를 여러 개 해야 한다면, 중괄호 내 반점을 이용해 { useState, useEffect }처럼 작성하면 됩니다.
  • 코드 내 함수형 컴포넌트에서 State를 사용하기 위해 useState를 사용하고 있습니다.

 

- 정리가 필요한 함수형 컴포넌트

정리가 필요한 함수형 컴포넌트에 대해 알아봅시다!

클래스형 컴포넌트에서 componentWillUnmount() 메소드를 통해 실행하던 코드를 useEffect를 이용해 실행해야 합니다.

이때 정리가 필요한 컴포넌트는 아래처럼 useEffect 내 함수의 반환을 통해 구현합니다.

Mounting과 Updating 시 실행할 코드는 구현한 함수 위에 작성해주면 됩니다.

useEffect(() => {
    // Mounting 및 Updating 시 실행할 코드
    return function 함수명() {
      // Unmounting 시 실행할 코드
    }
});

이렇게 함으로써 생명주기 메소드를 함수형 컴포넌트 한 곳에서 관리할 수 있습니다.

컴포넌트가 정리될 때, 즉 Unmounting 되는 시점에 useEffect에서 메소드가 반환됩니다!

 

예시 코드를 확인하며 알아봅시다.

// 코드블럭 4
// App.js 파일의 코드


import "./App.css";
import React, { useState, useEffect } from "react";

function Child() {
  // useEffect() 내 경고 문구를 출력하는 cleanup() 메소드를 반환하세요.

  useEffect(() => {
      // Mounting 및 Updating 시 실행할 코드
      return function unMounting () {
          alert("텍스트가 제거 되었습니다!")
          // Unmounting 시 실행할 코드
      }
  })

  return <p>버튼을 클릭해 해당 텍스트를 제거하세요.</p>;
}

function App() {
  const [show, setShow] = useState(true);

  let myheader;
  if (show) {
    myheader = <Child />;
  }

  return (
    <div>
      {myheader}
      <button onClick={() => setShow(false)}>버튼</button>
    </div>
  );
}

export default App;

Tips!

  • useEffect에서 반환되는 함수명은 자유롭게 설정해도 됩니다. 이름이 없는 화살표 함수를 반환해도 괜찮습니다.

 

[3] Effect Hook 요약 및 결론

지금까지 배운 내용을 바탕으로 Effect Hook을 이용한 프로그램을 통해 useEffect()를 이용하는 또 다른 방법을 알아보겠습니다. 앞의 실습들과 달리 대괄호를 이용해 특정 State의 변화를 확인할 수 있습니다.

 

아래 코드를 참고해주세요.

useEffect(()=>{},[특정변수 혹은 오브젝트]);

useEffect()에서 대괄호 안에 변수 혹은 오브젝트를 입력하면 입력한 것이 변화할 때 useEffect()가 호출이 됩니다.

코드에서 useEffect()안의 대괄호 안에 입력한 것을 변화 시킬때 총 3가지의 케이스가 있습니다.

 

a. 대괄호안에 정석적으로 변수나 오브젝트를 작성한 경우 => 대괄호 안에 변수 or 오브젝트가 입력되면 호출됩니다.

b. 대괄호를 useEffect()의 두번째 인자로서 할당하고, 빈 칸으로 두는 경우 => 괄호가 비어있으면 변화에 반응하지 않습니다.(최초 렌더링 혹은 컴포넌트 해제 시 호출)

c. 대괄호 자체를 지정하지 않은 경우(useEffect()의 인자로 함수만을 전달한 경우) => 대괄호 자체를 지정하지 않아도 useEffect()를 사용할 수 있습니다.

 

추가적으로 useEffect()가 여러 개 존재할 수도 있습니다.

 

아래 코드는 텍스트를 입력받고 그대로 화면에 띄우는 간단한 코드입니다.

단, 10초가 지나면 텍스트를 입력받고 띄우는 컴포넌트가 종료되도록 설정되어 있습니다.

// 코드블럭 5
// App.js 파일의 코드

// 코드 아래에 있는 App 컴포넌트에서 Example 컴포넌트를 10초 동안만 불러옵니다. 
// 프로그램 최초 실행 시 두 번째 useEffect()가 한 번 호출되고 
// 10초간 입력되는 테스트 변화에 따라 첫 번째 useEffect()가 호출됩니다. 
// 그리고 10초가 지난 뒤 두 번째 useEffect()가 다시 호출됩니다.

import "./App.css";
import React, { useState, useEffect } from "react";

const Example = () => {
  const [username, setUsername] = useState("");

  // 언제 호출되는지 확인하기 위해 useEffect에서 콘솔 출력을 해보세요.
  
  useEffect(() => {
      console.log("effect")
  }, [username]);

  useEffect(() => {
    return () => {
        console.log("clean up")
    };
  }, []);

  const handleUsername = (e) => {
    const { value } = e.target;

    setUsername(value);
  };

  return (
    <div>
      <div>
        <input value={username} onChange={handleUsername} />
      </div>
      <div>
        <span>{username}</span>
      </div>
    </div>
  );
};

function App() {
  const [shouldRender, setShouldRender] = useState(true);

  useEffect(() => {
    setTimeout(() => {
      setShouldRender(false);
    }, 10000);
  }, []);

  return shouldRender ? <Example /> : null;
}
export default App;

// 코드 내 존재하는 두 개의 useEffect()가 언제 호출되는지 콘솔 출력으로 확인하세요.

Tips!

  • Updating을 확인하기 위해서는 텍스트를 입력해보세요.
  • 출력 결과에서 Mounting 및 Updating 시 effect, Unmounting 시 cleaned up이 콘솔에 출력되는 모습입니다.

 

 

여기까지 React의 Effect Hook에 대해 살펴보았습니다! 

내용에 대해 추가할 점 및 보완할 점이 있으면 댓글로 피드백 부탁드립니다!

긴 글 읽어주셔서 감사드리고 다음 글에서 뵐게요~ 

 

냐앙

댓글