[멋사] 4주차 사전과제 - useState, 생명주기, useEffect, side effect

·2023년 4월 28일
0

likelion

목록 보기
4/14
  1. useState 문법
  2. useEffect와 컴포넌트 생명주기

1. useState

1-1. Hook이란?

Hook이란 특별한 함수를 말한다. 예를들어, useState는 state를 함수 컴포넌트 안에서 사용할 수 있게 해준다.

import { useState } from 'react'; //  useState 사용하기 위해 임포트

function Example() {
  //...
}

1-2. useState 기본형

import { useState } from 'react';

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

useState() 인자로 넘겨주는 값은 state의 초기값이다. 여기서는 0이 초기값이다. useStatestate 변수와 해당 변수 상태를 변경하는 함수 이 두가지 쌍을 반환한다.
(클래스 컴포넌트의 this.state.countthis.setState와 유사하다.)

위에 예시에서, count라는 변수(초깃값 0)와, 이 변수의 값을 변경하는 setCount 함수 이 두 가지 쌍이 반환된다.

1-3. state 가져오기

함수 컴포넌트는 바로 호출이 가능하다.

<p>You clicked {count} times</p>

count 변수를 직접 사용할 수 있다. (클래스에서는 this.state.count로 사용한다.)

1-4. state 갱신하기

this를 호출하는 것 없이, setCount 함수로 count 값의 상태를 변경할 수 있다.

<button onClick={() => setCount(count + 1)}>
  Click me!
</button>

2. 생명주기 (LifeCycle)

LifeCycle

  • 각각의 컴포넌트들은 생성 ➡️ 업데이트 ➡️ 제거 단계를 겪는 생명주기(LifeCycle)를 가진다.

LifeCycle API

  • 컴포넌트가 DOM 위에 생성되기 전 후 데이터가 변경되어 상태를 업데이트 하기 전 후로 실행되는 메소드를 말한다.

컴포넌트가 DOM 위에 생성될 때 [컴포넌트가 처음 화면에 렌더링]
컴포넌트가 DOM 위에 사라지기 전 [컴포넌트가 화면에 지워짐]
데이터가 변경되어 상태를 업데이트 한 후 [데이터가 변경될 때]

  • React 컴포넌트 rendering 할 때 나오는 메소드

    constructor
    componentWillMount (레거시)
    render
    componenetDidMount

  • React 상태 값 변경시 나오는 메소드

    componentDidUpdate

  • 컴포넌트 제거시 나오는 메소드

    componentWillUnmount

LifeCycle API 메소드는 클래스 컴포넌트에서만 사용이 가능하다. 함수 컴포넌트에서는 useEffect Hook이 그 역할을 대신한다.

3. useEffect

3-1. useEffect란?

  • useEffect 함수는 리액트 컴포넌트가 렌더링 될 때마다 특정 작업을 실행할 수 있도록 하는 Hook을 말한다.

useEffect가 하는 일?

  • useEffect Hook을 이용하여 우리는 React에게 컴포넌트가 렌더링 이후에 어떤 일을 수행해야 하는 지를 말해줄 수 있다.
  • React는 우리가 넘긴 함수를 기억했다가 (이 함수를 effect라고 부름) DOM 업데이트를 수행한 이후에 불러낸다.

+) useEffect는 Hook을 componentDidMountcomponenetDidUpdate, componentWillUnmount가 합쳐진 것으로도 생각할 수 있다.

3-2. Side Effect란?

  • Side Effect는 실행 중에 어떤 객체를 접근해서 변화가 일어나는 행위를 말한다.
  • 데이터 가져오기, 구독(subscription) 설정하기, 수동으로 React 컴포넌트의 DOM을 수정하기, 함수에서 전역변수의 값을 변경하기 등 이 모든 것이 Side Effect이다.
  • useEffect는 함수 컴포넌트에서 Side Effect를 처리하기 위해 사용한다.
    -> 함수가 매개변수를 받아 결과를 생성하는 것과 무관한 외부의 상태를 변경하거나 외부와 상호작용해야 하는 코드는 useEffect 함수를 통해 분리한다.
import { 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>
  );
}

Side Effect 종류

정리 (clean-up)가 필요한 것 / 그렇지 않은 것
(React가 DOM으로 업데이트 한 뒤 추가로 코드를 실행해야 하는 경우)

정리(Clean-up)를 이용하지 않는 Effect

  • 네트워크 리퀘스트, DOM 수동 조작, 로깅 등의 경우에는 실행 이후 신경 쓸 것이 없으므로 정리를 이용하지 않는다.

Class를 사용하는 경우
React의 클래스 컴포넌트에서 render 메서드 그 자체는 side effect를 실행시키지 않는다.
(effect를 수행하는 시기는 React가 DOM을 업데이트 한 이후이다.)

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = 
      { 
      count: 0 
    };
  }
  
  componentDidMount() { // side effect
    document.title = `You clicked ${this.state.count} times`;
  }
  componentDidUpdate() { // side effect 
    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>
	);
  }
}

React class에서 side effectcomponentDidMountcomponentDidUpdate에 두는 것은 effect를 수행하는 시점이 리액트가 DOM을 업데이트 한 이후이기 때문이다.
이는 컴포넌트가 이제 막 마운트된 단계인지, 아니면 업데이트되는 단계인지에 상관없이 같은 side effect를 수행해야 하기 때문이다. 렌더링 이후에는 항상 같은 코드가 수행되기를 바라는 것. -> 하지만 React 클래스 컴포넌트는 그러한 메서드를 갖고있지 않다.
따라서 함수를 별개의 메서드로 뽑아낸다고 해도 여전히 두 장소에서 함수를 불러내야 하는 코드 중복이 일어난다.

Hook(useEffect)을 사용하는 경우

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

위의 경우에는 effect를 통해 문서 타이틀을 지정하지만, 이 외에도 데이터를 가져오거나 다른 명령형(imperative) API를 불러내는 일도 할 수 있다.
useEffect를 컴포넌트 안에서 불러내는 이유는 컴포넌트 내부에 둠으로써 effect를 통해 count라는 state 변수(혹은 그 어떤 prop)에 접근할 수 있기 때문이다. 함수 범위 안에 존재하므로 특별한 API 없이도 값을 얻을 수 있다. -> Hook은 React에 한정된 API를 고안하는 것보다 자바스크립트가 이미 갖고 있는 방법을 이용하여 문제를 해결한다.
또한, useEffect는 렌더링 이후 매번 수행된다. 리액트는 effect가 수행되는 시점에 이미 DOM이 업데이트 되었음을 보장한다.

정리(Clean-up)를 이용하는 Effect

  • 리액트가 DOM으로 업데이트 한 뒤, 외부 데이터에 구독을 설정해야하는 이러한 경우에는 메모리 누수가 발생하지 않도록 정리(clean-up)하는 것이 매우 중요하다.

Class를 사용하는 경우

  • React Class에서는 componentDidMount에 구독(subscription)을 설정한 뒤 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';
  }
}

componentDidMountcomponentWillUnmount 이 두 메서드 내에 개념상 똑같은 effect에 대한 코드가 있음에도 불구하고, 생명주기 메서드는 이를 분리하게 만든다.

Hook을 사용하는 경우

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가 필요한 게 아닌, useEffect는 이를 함께 다루도록 고안되었다. effect가 함수를 반환하면 리액트는 그 함수를 정리가 필요한 때에 실행시킨다.
effect에서 함수를 반환하는 이유는 effect를 위한 추가적인 정리 메커니즘이다. 구독의 추가와 제거가 모두 하나의 effect를 구성한다.
리액트가 effect를 정리하는 정확한 시점은 컴포넌트가 마운트 해제되는 때에 정리를 실행한다. 단, effect는 렌더링이 실행될 때마다 실행되기 때문에 effect를 실행하기 전 이전의 렌더링에서 파생된 effect를 정리하는 이유가 바로 이 때문이다. -> 버그를 방지하고, 성능 저하 문제가 발생할 경우 effect를 건너뛸 수 있다.

➡️ effect Hook은 정리가 필요한 경우에는 함수를 반환하고, 정리가 필요하지 않는 경우에는 그 어떤것도 반환하지 않는다. 이처럼 effect Hook은 이 두 가지 경우를 한 개의 API로 통합한다.

0개의 댓글