개발정보

React(리액트) Hooks

쿠카곰돌이 2022. 11. 23. 17:24
반응형

Hook은 React 버전 16.8부터 React 요소로 새로 추가되었습니다.

 

Hook을 이용하여 기존 Class 바탕의 코드를 작성할 필요 없이 상태 값과 여러 React의 기능을 사용할 수 있습니다.

import React, { useState } from 'react';

function Example() {
  // "count"라는 새로운 상태 값을 정의합니다.  const [count, setCount] = useState(0);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

useState는 우리가 “Hook”에서 처음 배우게 될 함수입니다.

 

이 예시는 단지 맛보기에 불과합니다.

아직 이해되지 않아도 걱정하지 마세요!

이 페이지에서는 우리가 왜 Hook을 React에 추가했는지, 그리고 Hook이 애플리케이션을 작성하는 데 어떠한 도움을 주는지 설명할 것입니다.

주의

React 16.8.0은 Hook을 지원하는 첫 번째 배포입니다. 업그레이드 시 React DOM을 포함한 모든 패키지의 업데이트를 진행해주세요. React Native는 v0.59부터 Hook을 지원합니다.

 

컴포넌트 사이에서 상태 로직을 재사용하기 어렵습니다.

React는 컴포넌트간에 재사용 가능한 로직을 붙이는 방법을 제공하지 않습니다.

(예를 들어, 스토어에 연결하는 것)

 

이전부터 React를 사용해왔다면, render props이나 고차 컴포넌트와 같은 패턴을 통해 이러한 문제를 해결하는 방법에 익숙할 것입니다.

 

이런 패턴의 사용은 컴포넌트의 재구성을 강요하며, 코드의 추적을 어렵게 만듭니다.

 

React 개발자 도구에서 React 애플리케이션을 본다면, providers, consumers, 고차 컴포넌트, render props 그리고 다른 추상화에 대한 레이어로 둘러싸인 “래퍼 지옥(wrapper hell)“을 볼 가능성이 높습니다. 

 

개발자 도구에서 걸러낼 수 있지만, 이 문제의 요점은 심층적이었습니다.

 

React는 상태 관련 로직을 공유하기 위해 좀 더 좋은 기초 요소가 필요했습니다.

 

Hook을 사용하면 컴포넌트로부터 상태 관련 로직을 추상화할 수 있습니다.

 

이를 이용해 독립적인 테스트와 재사용이 가능합니다. 

 

Hook은 계층의 변화 없이 상태 관련 로직을 재사용할 수 있도록 도와줍니다. 

 

이것은 많은 컴포넌트 혹은 커뮤니티 사이에서 Hook을 공유하기 쉽게 만들어줍니다.

 

복잡한 컴포넌트들은 이해하기 어렵습니다.

우리는 때론 간단하게 시작했지만 관리하기가 힘들어지는 상태 관련 로직들과 사이드 이펙트가 있는 컴포넌트들을 유지보수해야 합니다.

 

각 생명주기 메서드에는 자주 관련 없는 로직이 섞여들어가고는 합니다.

 

예시로 componentDidMount  componentDidUpdate는 컴포넌트안에서 데이터를 가져오는 작업을 수행할 때 사용 되어야 하지만, 같은 componentDidMount에서 이벤트 리스너를 설정하는 것과 같은 관계없는 로직이 포함되기도 하며, componentWillUnmount에서 cleanup 로직을 수행하기도 합니다.

 

함께 변경되는 상호 관련 코드는 분리되지만 이와 연관 없는 코드들은 단일 메서드로 결합합니다.

 

이로 인해 버그가 쉽게 발생하고 무결성을 너무나 쉽게 해칩니다.

 

위와 같은 예시에서, 상태 관련 로직은 한 공간안에 묶여 있기 때문에 이런 컴포넌트들을 작게 분리하는 것은 불가능하며 테스트하기도 어렵습니다.

 

이 때문에 많은 사용자들은 React를 별도의 상태 관리 라이브러리와 함께 결합해서 사용해왔습니다. 그러나, 이런 상태 관리 라이브러리는 종종 너무 많은 추상화를 하고, 서로 다른 파일들 사이에서 건너뛰기를 요구하며 컴포넌트 재사용을 더욱더 어렵게 만들었습니다.

 

이같은 문제를 해결하기위해, 생명주기 메서드를 기반으로 쪼개는 것 보다는, Hook을 통해 서로 비슷한 것을 하는 작은 함수의 묶음으로 컴포넌트를 나누는 방법을 사용할 수 있습니다. (구독 설정 및 데이터를 불러오는 것과 같은 로직) 또한 이러한 로직의 추적을 쉽게 할 수 있도록 리듀서를 활용해 컴포넌트의 지역 상태 값을 관리하도록 할 수 있습니다.

이에 대해 Effect Hook에서 더 알아볼 것입니다.

 

Class은 사람과 기계를 혼동시킵니다.

React 에서의 Class 사용을 위해서는 JavaScript의 this 키워드가 어떻게 작동하는지 알아야만 합니다. JavaScript의 this키워드는 대부분의 다른 언어에서와는 다르게 작동함으로 사용자에게 큰 혼란을 주었으며, 코드의 재사용성과 구성을 매우 어렵게 만들고는 했습니다. 또한 class의 사용을 위해 이벤트 핸들러가 등록되는 방법을 정확히 파악해야 했으며, 이는 ES2022 public class fields가 없을 때 코드를 매우 장황하게 만들었습니다. 사용자들은 props, state, 그리고 top-down 데이터 흐름을 완벽하게 하고도, Class의 이해에는 어려움을 겪고는 했습니다. React 내의 함수와 Class 컴포넌트의 구별, 각 요소의 사용 타이밍 등은 숙련된 React 개발자 사이에서도 의견이 일치하지 않습니다.

React는 지난 5년 동안 널리 사용되어 왔으며, React의 개발진은 5년 뒤에도 React가 지금과 같이 널리 이용되길 원합니다. Svelte, Angular, Glimmer 등에서 보여주듯이, 컴포넌트를 미리 컴파일해놓는 방식에는 높은 잠재력이 있습니다. 템플릿에 한정하지 않는다면 더 그렇고요. 개발진은 최근 Prepack을 사용한 컴포넌트 folding에 대해서 실험해왔고 긍정적인 결과를 보았습니만, Class 컴포넌트가 이러한 최적화를 더 느린 경로로 되돌리는 의도하지 않은 패턴을 장려할 수 있다는 것을 발견했습니다. Class는 최근 사용되는 도구에서도 많은 문제를 일으킵니다. 예를 들어 Class는 코드의 최소화를 힘들게 만들고, 핫 리로딩을 깨지기 쉽고 신뢰할 수 없게 만듭니다. 우리는 코드가 최적화 가능한 경로에서 유지될 가능성이 더 높은 API를 제공하길 원하였습니다.

이러한 문제를 해결하기 위해, Hook은 Class없이 React 기능들을 사용하는 방법을 제시합니다. 개념적으로 React 컴포넌트는 항상 함수에 더 가깝습니다. Hook은 React의 정신을 희생하지 않고 함수의 사용을 권장합니다. Hook은 명령형 코드로 해결책을 찾을 수 있게 해주며 복잡한 함수형 또는 반응형 프로그래밍 기술을 배우도록 요구하지 않습니다.

 

 

 

1) Class 컴포넌트 vs Function 컴포넌트

리액트의 컴포넌트는 클래스형과 함수형으로 나눠진다. hook이 나오기 전 까지는 상태값(state)에 접근, 생명주기 기능(lifecycle features)을 사용하기 위해 class형 컴포넌트 선언을 해줘야 했다. 함수형 컴포넌트는 한번 호출되고 메모리상에서 사라져 상태값 접근과 라이프사이클 구현이 불가능했다. 그러나 class형 컴포넌트를 사용하다보니 아래와 같은 문제점들이 발생했다

2) Class형 컴포넌트의 문제점

  • 컴포넌트간에 로직의 재사용이 불가능하다
    • render props나 HOC(High order component, 고차 컴포넌트)와 같은 패턴을 통해 문제를 해결할 수 있지만 이러한 패턴은 코드의 추적을 어렵게 만든다
    • 계속해서 이런 코드를 사용하게 되다보면 많은 레이어들로 둘러쌓인 wrpper hell을 겪게 된다
  • 코드가 복잡해진다

이러하듯 class컴포넌트가 지닌 단점에도 불구하고 상태값 접근과 생명주기 관리 때문에 class형 컴포넌트를 사용해야됐다

💿 Hook의 등장!

  • 리액트 hook은 React 16.8버전에 새로 추가된 기능으로, 함수형 컴포넌트에서 react state와 생명주기 기능(lifecycle features)을 "연동(hook into)할 수 있게 해준다. useState를 예시로 들면 class를 사용하지 않고도 상태를 가질 수 있게 된 것이다.
    • Hook은 Class 안에서는 동작하지 않는다.
  • 이전에 리액트가 겪던 문제들을 해결해주며 클래스형 컴포넌트를 사용하지 않고도 함수형 컴포넌트에서 상태값 접근과 자식 요소에 접근이 가능해졌다
  • 리액트는 useState, useEffect 같은 내장 Hook을 몇 가지 제공한다
  • 또한 props, state, context, refs 그리고 lifecycle과 같은 리액트 개념에 좀 더 직관적인 API를 제공한다. 또한 이 개념들을 엮기 위해 새로운 방법을 제공한다
    • 컴포넌트 간에 상태 관련 로직을 재사용하기 위해 hook을 직접 만드는 것도 가능하다

💿 Class / Function 코드비교

클래스형

import React, { Component } from 'react';

class Example extends Component {
  state = {
    count: 0,
  };

setCount(num) {
  this.setState({
    count: num,
  });
}
render() {
  const { count } = this.state;
  return (
    <div>
     <div>
      <p>You clicked {count} times</p>
      <button
       onClick={() => {
        this.setCount(count + 1);
     }}
    >
       Click Me!
     </button>
    </div>
   </div>
  );
 )
}

export default Example;

함수형

import React, { useState } from 'react';

function Example() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
     <p>You clicked {count} times</p>
     <button onClick={() => setCount(count + 1)}>
       Click Me!
       </button>
      </div>
  );
}

export default Example;
  • this와 render없이도 상태값 접근이 가능해졌다!

여러 state 변수 사용하기

  • 하나의 컴포넌트 내에서 state hook을 여러 개 사용할 수도 있다
import React, { useState } from 'react';

funtion App() {
  const [name, setName] = ('');
  const [age, setAge] = (35);
  const [coffee, setCoffee] = ('Latte');
}

export default App;

구조분해할당 포스팅 보러가기 => 구조분해 할당

Hook의 장점

  • 컴포넌트로부터 상태 관련 로직을 추상화할 수 있다
    • 이를 이용해 독립적인 재사용이 가능하다
    • 코드의 재사용은 가독성을 높이고 유지보수를 용이하게 한다
  • 훅은 계층의 변화 없이 상태 관련 로직을 재사용할 수 있다
  • 생명주기 메서드를 기반으로 쪼개는 것 보다는 훅을 통해 작은 함수의 묶음으로 컴포넌트를 나누는 방법을 사용할 수 있다

Hook의 규칙

1) 최상위 에서만 훅을 호출해야 한다. 훅을 호출하는 순서는 항상 같아야 한다

  • 반복문, 조건문, 중첩된 함수 내에서 Hook을 사용하면 안된다.
  • 왜 훅의 호출 순서가 같아야 하는 걸까?
    • 리액트가 상태값을 구분할 수 있는 유일한 정보는 훅이 사용된 순서이기 때문이다. 리액트가 훅이 호출된 순서에 의존한다는 것이다.
    • 예시로 반복문 안에서 훅을 호출했을 때 반복문이 true라면 괜찮겠지만 값이 false라면 건너뛰게 된다. 이렇게 하면 실행순서가 바뀔 수 있어 오류를 일으킨다
    • 조건문 혹은 반복문을 사용하고 싶을때는 useEffect안에 넣어 사용하면 된다.

2) React 함수 컴포넌트 내에서만 Hook을 호출해야 한다

  • 일반 JS함수에서는 훅을 호출하면 안된다
  • 직접 작성한 custom hook에서는 사용이 가능하다

 

 

자신만의 Hook 만들기

Hook은 React 16.8 버전에 새로 추가되었습니다. Hook은 클래스 컴포넌트를 작성하지 않아도 state와 같은 특징들을 사용할 수 있습니다.

자신만의 Hook을 만들면 컴포넌트 로직을 함수로 뽑아내어 재사용할 수 있습니다.

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);    return () => {      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);    };  });
  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

이제 채팅 애플리케이션에 연락처 목록이 있으며 그중에서 온라인 상태인 사용자들의 이름을 초록색으로 표시하는 상황을 가정해 보겠습니다. 위의 코드와 비슷한 로직을 복사하여 FriendListItem 컴포넌트 안에 붙여넣을 수도 있지만, 가장 좋은 방법이라고 할 수는 없습니다.

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

function FriendListItem(props) {
  const [isOnline, setIsOnline] = useState(null);  useEffect(() => {    function handleStatusChange(status) {      setIsOnline(status.isOnline);    }    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);    return () => {      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);    };  });
  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}

그 대신 이 로직을 FriendStatus와 FriendListItem에서 공유하도록 하고 싶습니다.

React에는 상태 관련 로직을 컴포넌트에서 공유하는 두 가지 전통적인 방법이 있는데 render props 고차 컴포넌트입니다. Hook을 사용하여 트리에 컴포넌트를 더하지 않고 위의 문제를 해결하는 방법을 보도록 하겠습니다.

사용자 정의 Hook 추출하기

두 개의 자바스크립트 함수에서 같은 로직을 공유하고자 할 때는 또 다른 함수로 분리합니다. 컴포넌트와 Hook 또한 함수이기 때문에 같은 방법을 사용할 수 있습니다!

사용자 정의 Hook은 이름이 use로 시작하는 자바스크립트 함수입니다. 사용자 Hook은 다른 Hook을 호출할 수 있습니다. 예를 들자면, 아래의 useFriendStatus가 우리의 첫 번째 사용자 정의 Hook입니다.

import { useState, useEffect } from 'react';

function useFriendStatus(friendID) {  const [isOnline, setIsOnline] = useState(null);

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

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}

딱히 새로운 것은 없습니다. 로직은 위의 컴포넌트로부터 복사해왔습니다. 다만 컴포넌트에서처럼 다른 Hook들은 사용자 Hook의 위로 놓여야 하며 사용자 정의 Hook은 조건부 함수가 아니어야 합니다.

React 컴포넌트와는 다르게 사용자 정의 Hook은 특정한 시그니처가 필요 없습니다. 무엇을 인수로 받아야 하며 필요하다면 무엇을 반환해야 하는 지를 사용자가 결정할 수 있습니다. 다시 말하지만, 보통의 함수와 마찬가지입니다. 이름은 반드시 use로 시작해야 하는데 그래야만 한눈에 보아도 Hook 규칙이 적용되는지를 파악할 수 있기 때문입니다.

useFriendStatus Hook의 목표는 친구의 상태를 구독하기 위함입니다. 이를 위하여 friendID를 인수로 받고 온라인 상태의 여부를 반환합니다.

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  // ...

  return isOnline;
}

이제 사용자 정의 Hook을 어떻게 이용하는지 보겠습니다.

사용자 정의 Hook 이용하기

처음 우리의 목표는 FriendStatus와 FriendListItem 컴포넌트에 중복되어있는 로직을 제거하는 것이었습니다. 두 컴포넌트 모두 친구의 온라인 상태 여부를 알아야 하죠.

이제 이 로직을 useFriendStatus hook으로 뽑아내었으니, 바로 사용할 수 있습니다.

function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);
  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);
  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}

본래의 예시와 동일한 코드인가요? 네 정확히 같은 방식으로 작동합니다. 자세히 보면 작동방식에 어떤 변화도 없다는 것을 알 수 있습니다. 바뀐 것은 오로지 공통의 코드를 뽑아내 새로운 함수로 만든 것뿐입니다. 사용자 정의 Hook은 React의 특별한 기능이라기보다 기본적으로 Hook의 디자인을 따르는 관습입니다.

사용자 정의 Hook의 이름은 “use”로 시작되어야 하나요? 네 그렇습니다. 이 관습은 아주 중요합니다. 이를 따르지 않으면 특정한 함수가 그 안에서 Hook을 호출하는지를 알 수 없기 때문에 Hook 규칙의 위반 여부를 자동으로 체크할 수 없습니다.

같은 Hook을 사용하는 두 개의 컴포넌트는 state를 공유하나요? 아니요. 사용자 정의 Hook은 상태 관련 로직(구독을 설정하고 현재 변숫값을 기억하는 것)을 재사용하는 메커니즘이지만 사용자 Hook을 사용할 때마다 그 안의 state와 effect는 완전히 독립적입니다.

사용자 정의 Hook은 어떻게 독립된 state를 얻는 건가요? 각각의 Hook에 대한 호출은 서로 독립된 state를 받습니다. useFriendStatus를 직접적으로 호출하기 때문에 React의 관점에서 이 컴포넌트는 useState와 useEffect를 호출한 것과 다름없습니다. 또한 우리가 이전에 배웠듯이, 하나의 컴포넌트 안에서 useState와 useEffect를 여러 번 부를 수 있고 이들은 모두 완전히 독립적입니다.

팁: Hook에서 Hook으로 정보 전달하기

Hook은 함수이기 때문에 Hook 사이에서도 정보를 전달할 수 있습니다.

상황설명을 위해 채팅 예시에 있는 다른 컴포넌트를 사용하겠습니다. 현재 선택된 친구가 온라인 상태인지를 표시하는 채팅 수신자 선택기입니다.

const friendList = [
  { id: 1, name: 'Phoebe' },
  { id: 2, name: 'Rachel' },
  { id: 3, name: 'Ross' },
];

function ChatRecipientPicker() {
  const [recipientID, setRecipientID] = useState(1);  const isRecipientOnline = useFriendStatus(recipientID);
  return (
    <>
      <Circle color={isRecipientOnline ? 'green' : 'red'} />      <select
        value={recipientID}
        onChange={e => setRecipientID(Number(e.target.value))}
      >
        {friendList.map(friend => (
          <option key={friend.id} value={friend.id}>
            {friend.name}
          </option>
        ))}
      </select>
    </>
  );
}

현재 선택된 친구의 ID를 recipientID state 변수에 저장하고 사용자가 <select> 선택기에 있는 다른 친구를 선택하면 이를 업데이트합니다.

useState Hook 호출은 recipientID state 변수의 최신값을 돌려주기 때문에 이를 useFriendStatus Hook에 인수로 보낼 수 있습니다.

  const [recipientID, setRecipientID] = useState(1);
  const isRecipientOnline = useFriendStatus(recipientID);

이를 통해 지금 선택되어있는 친구의 온라인 상태 여부를 알 수 있습니다. 다른 친구를 선택하고 recipientID state 변수를 업데이트하면 useFriendStatus Hook은 이미 선택되어있는 친구의 구독을 해지하고 새로이 선택된 친구의 상태를 구독할 것입니다.

useYourImagination()

사용자 정의 Hook은 이전 React 컴포넌트에서는 불가능했던 로직공유의 유연성을 제공합니다. 사용자 정의 Hook을 만들어 폼 다루기, 애니메이션, 선언형 구독, 타이머, 그 외에 생각하지 않은 부분까지 훨씬 다양한 쓰임새에 적용할 수 있습니다. 또한 React의 내장된 기능만큼이나 사용하기 쉬운 Hook을 만들 수도 있습니다.

너무 이른 단계에서 로직을 뽑아내려고 하지는 않는 게 좋습니다. 함수 컴포넌트가 할 수 있는 일이 더 다양해졌기 때문에 여러분의 코드에 있는 함수 컴포넌트의 길이도 길어졌을 것입니다. 이는 지극히 평범한 일이며 지금 바로 Hook으로 분리해야만 한다고 느낄 필요는 없습니다. 하지만 동시에 사용자 정의 Hook이 복잡한 로직을 단순한 인터페이스 속에 숨길 수 있도록 하거나 복잡하게 뒤엉킨 컴포넌트를 풀어내도록 돕는 경우들을 찾아내는 것을 권장합니다.

예를 들자면, 내부에 많은 state를 지니고 있지만, 이것들이 적절하게 관리되지 않는 컴포넌트가 있다고 가정하겠습니다. useState는 업데이트 로직을 모아주는 데에는 도움이 되지 않기 때문에 대신 Redux reducer의 이용을 선호할 수도 있겠지요.

function todosReducer(state, action) {
  switch (action.type) {
    case 'add':
      return [...state, {
        text: action.text,
        completed: false
      }];
    // ... other actions ...
    default:
      return state;
  }
}

reducer는 독립적으로 테스트하기에 편리하며 복잡한 업데이트 로직의 표현이 늘어나는 경우에도 잘 맞습니다. 필요하다면 더 작은 reducer로 나누는 것도 가능합니다. 하지만 React state의 장점을 누리는 것은 선택일 뿐, 이 때문에 또 다른 라이브러리를 설치하고 싶지 않을 수도 있습니다.

컴포넌트의 안의 state를 reducer로 관리하는 useReducer Hook을 작성한다면 어떨까요? 이 Hook을 간단히 표현하면 다음과 같습니다.

function useReducer(reducer, initialState) {
  const [state, setState] = useState(initialState);

  function dispatch(action) {
    const nextState = reducer(state, action);
    setState(nextState);
  }

  return [state, dispatch];
}

컴포넌트 안에서 이 Hook을 사용하여 reducer가 state 관리를 합니다.

function Todos() {
  const [todos, dispatch] = useReducer(todosReducer, []);
  function handleAddClick(text) {
    dispatch({ type: 'add', text });
  }

  // ...
}

복잡한 컴포넌트에서 내에서 state를 reducer로 관리해야 하는 보편적 필요성을 고려하여 React에는 useReducer가 내장되어 있습니다. 이는 Hook API 참고서에서 다른 내장 Hook과 함께 찾아볼 수 있습니다.

 

 

 

Using the Effect Hook

Hooks는 React 16.8버전에 새로 추가되었습니다. Hook은 클래스 컴포넌트를 작성하지 않아도 state와 같은 특징들을 사용할 수 있습니다.

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

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

위의 코드는 이전 페이지의 카운터 예시를 바탕으로 하지만, 문서의 타이틀을 클릭 횟수가 포함된 문장으로 표현할 수 있도록 새로운 기능을 더했습니다.

데이터 가져오기, 구독(subscription) 설정하기, 수동으로 React 컴포넌트의 DOM을 수정하는 것까지 이 모든 것이 side effects입니다. 이런 기능들(operations)을 side effect(혹은 effect)라 부르는 것이 익숙하지 않을 수도 있지만, 아마도 이전에 만들었던 컴포넌트에서 위의 기능들을 구현해보았을 것입니다.

React의 class 생명주기 메서드에 친숙하다면, 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에 두는 것이 바로 이 때문입니다. 예시로 돌아와서 React가 DOM을 바꾸고 난 뒤 문서 타이틀을 업데이트하는 React counter 클래스 컴포넌트를 봅시다.

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를 수행해야 하기 때문입니다. 개념적으로 렌더링 이후에는 항상 같은 코드가 수행되기를 바라는 것이죠. 하지만 React 클래스 컴포넌트는 그러한 메서드를 가지고 있지 않습니다. 함수를 별개의 메서드로 뽑아낸다고 해도 여전히 두 장소에서 함수를 불러내야 합니다.

이제 useEffect Hook에서 같은 기능을 어떻게 구현하는지 보겠습니다.

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를 통해 문서 타이틀을 지정하지만, 이 외에도 데이터를 가져오거나 다른 명령형(imperative) API를 불러내는 일도 할 수 있습니다.

useEffect를 컴포넌트 안에서 불러내는 이유는 무엇일까요? useEffect를 컴포넌트 내부에 둠으로써 effect를 통해 count state 변수(또는 그 어떤 prop에도)에 접근할 수 있게 됩니다. 함수 범위 안에 존재하기 때문에 특별한 API 없이도 값을 얻을 수 있는 것입니다. Hook은 자바스크립트의 클로저를 이용하여 React에 한정된 API를 고안하는 것보다 자바스크립트가 이미 가지고 있는 방법을 이용하여 문제를 해결합니다.

useEffect는 렌더링 이후에 매번 수행되는 걸까요? 네, 기본적으로 첫번째 렌더링과 이후의 모든 업데이트에서 수행됩니다.(나중에 effect를 필요에 맞게 수정하는 방법에 대해 다룰 것입니다.) 마운팅과 업데이트라는 방식으로 생각하는 대신 effect를 렌더링 이후에 발생하는 것으로 생각하는 것이 더 쉬울 것입니다. React는 effect가 수행되는 시점에 이미 DOM이 업데이트되었음을 보장합니다.

상세한 설명

effect에 대해 좀 더 알아보았으니 아래의 코드들을 더 쉽게 이해할 수 있을 것입니다.

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

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

count state 변수를 선언한 뒤 React에게 effect를 사용함을 말하고 있습니다. useEffect Hook에 함수를 전달하고 있는데 이 함수가 바로 effect입니다. 이 effect 내부에서 document.title이라는 브라우저 API를 이용하여 문서 타이틀을 지정합니다. 같은 함수 내부에 있기 때문에 최신의 count를 바로 얻을 수 있습니다. 컴포넌트를 렌더링할 때 React는 우리가 이용한 effect를 기억하였다가 DOM을 업데이트한 이후에 실행합니다. 이는 맨 첫 번째 렌더링은 물론 그 이후의 모든 렌더링에 똑같이 적용됩니다.

숙련된 자바스크립트 개발자라면 useEffect에 전달된 함수가 모든 렌더링에서 다르다는 것을 알아챘을지도 모릅니다. 이는 의도된 것으로서, count 값이 제대로 업데이트 되는지에 대한 걱정 없이 effect 내부에서 그 값을 읽을 수 있게 하는 부분이기도 합니다. 리렌더링하는 때마다 모두 이전과 다른 effect로 교체하여 전달합니다. 이 점이 렌더링의 결과의 한 부분이 되게 만드는 점인데, 각각의 effect는 특정한 렌더링에 속합니다. 이 페이지의 뒷부분에서 이것이 왜 유용한지에 대해서 더 자세히 다룰 것입니다.

componentDidMount 혹은 componentDidUpdate와는 달리 useEffect에서 사용되는 effect는 브라우저가 화면을 업데이트하는 것을 차단하지 않습니다. 이를 통해 애플리케이션의 반응성을 향상해줍니다. 대부분의 effect는 동기적으로 실행될 필요가 없습니다. 흔하지는 않지만 (레이아웃의 측정과 같은) 동기적 실행이 필요한 경우에는 useEffect와 동일한 API를 사용하는 useLayoutEffect라는 별도의 Hook이 존재합니다.

정리(clean-up)를 이용하는 Effects

위에서 정리(clean-up)가 필요하지 않은 side effect를 보았지만, 정리(clean-up)가 필요한 effect도 있습니다. 외부 데이터에 구독(subscription)을 설정해야 하는 경우를 생각해보겠습니다. 이런 경우에 메모리 누수가 발생하지 않도록 정리(clean-up)하는 것은 매우 중요합니다. class와 Hook을 사용하는 두 경우를 비교해보겠습니다.

Class를 사용하는 예시

React class에서는 흔히 componentDidMount에 구독(subscription)을 설정한 뒤 componentWillUnmount에서 이를 정리(clean-up)합니다. 친구의 온라인 상태를 구독할 수 있는 ChatAPI 모듈의 예를 들어보겠습니다. 다음은 class를 이용하여 상태를 구독(subscribe)하고 보여주는 코드입니다.

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에 대한 코드가 있음에도 불구하고 생명주기 메서드는 이를 분리하게 만듭니다.

주의

눈썰미가 좋은 독자들은 이 예시가 완전하기 위해서는 componentDidUpdate가 필요하다는 것을 눈치챘을 것입니다. 이에 대해서는 다음 섹션에서 다룰 것입니다.

Hook을 이용하는 예시

이제 이 컴포넌트를 Hook을 이용하여 구현해봅시다.

정리(clean-up)의 실행을 위해 별개의 effect가 필요하다고 생각할 수도 있습니다. 하지만 구독(subscription)의 추가와 제거를 위한 코드는 결합도가 높기 때문에 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는 정리를 위한 함수를 반환할 수 있습니다. 이 점이 구독(subscription)의 추가와 제거를 위한 로직을 가까이 묶어둘 수 있게 합니다. 구독(subscription)의 추가와 제거가 모두 하나의 effect를 구성하는 것입니다.

React가 effect를 정리(clean-up)하는 시점은 정확히 언제일까요? React는 컴포넌트가 마운트 해제되는 때에 정리(clean-up)를 실행합니다. 하지만 위의 예시에서 보았듯이 effect는 한번이 아니라 렌더링이 실행되는 때마다 실행됩니다. React가 다음 차례의 effect를 실행하기 전에 이전의 렌더링에서 파생된 effect 또한 정리하는 이유가 바로 이 때문입니다. 이것이 버그를 방지하는 데에 어떻게 도움이 되는지 그리고 성능 저하 문제가 발생할 경우 effect를 건너뛰는 방법에 대해서 이다음으로 논의해봅시다.

주의

effect에서 반드시 유명함수(named function)를 반환해야 하는 것은 아닙니다. 목적을 분명히 하기 위해 정리(clean-up)라고 부르고 있지만 화살표 함수를 반환하거나 다른 이름으로 불러도 무방합니다.

요약

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

이처럼 effect Hook은 두 가지 경우를 한 개의 API로 통합합니다.


effect hook이 어떻게 작동하는지에 대해 충분히 이해했거나, 내용이 이해하기 어렵다고 생각된다면 다음 페이지의 Hook의 규칙로 넘어가도 좋습니다.


effect를 이용하는 팁

이제 숙련된 React 사용자들이라면 보다 궁금해할 useEffect에 대해 좀 더 깊이 알아보겠습니다. 이 부분은 지금 바로 읽어야 하는 것은 아니며, 언제라도 effect hook의 자세한 이해가 필요할 때 돌아와서 읽어도 좋습니다.

팁: 관심사를 구분하려고 한다면 Multiple Effect를 사용합니다

Hook이 탄생한 동기가 된 문제 중의 하나가 생명주기 class 메서드가 관련이 없는 로직들은 모아놓고, 관련이 있는 로직들은 여러 개의 메서드에 나누어 놓는 경우가 자주 있다는 것입니다. 이전의 예시에서도 보았던 카운터와 친구의 상태 지표 로직을 결합한 컴포넌트를 보겠습니다.

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

  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

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

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  handleStatusChange(status) {
    this.setState({
      isOnline: status.isOnline
    });
  }
  // ...

document.title을 설정하는 로직이 componentDidMount와 componentDidUpdate에 나누어져 있습니다. 구독(subscription)로직 또한 componentDidMount와 componentWillUnmount에 나누어져 있네요. componentDidMount가 두 가지의 작업을 위한 코드를 모두 가지고 있습니다.

Hook을 이용하여 이 문제를 어떻게 해결할 수 있을까요? State Hook을 여러 번 사용할 수 있는 것처럼 effect 또한 여러 번 사용할 수 있습니다. Effect를 이용하여 서로 관련이 없는 로직들을 갈라놓을 수 있습니다.

function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  useEffect(() => {    document.title = `You clicked ${count} times`;
  });

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

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

Hook을 이용하면 생명주기 메서드에 따라서가 아니라 코드가 무엇을 하는지에 따라 나눌 수가 있습니다. React는 컴포넌트에 사용된 모든 effect를 지정된 순서에 맞춰 적용합니다.

설명: effect가 업데이트 시마다 실행되는 이유

class에 익숙하다면 왜 effect 정리(clean-up)가 마운트 해제되는 때에 한번만이 아니라 모든 리렌더링 시에 실행되는지가 궁금할 것입니다. 이러한 디자인이 버그가 적은 컴포넌트를 만드는 데에 어떻게 도움이 되는지 다음의 예시를 통해 알아봅시다.

이 페이지의 위에서 봤던 친구가 온라인인지 아닌지 표시하는 FriendStatus 컴포넌트 예시를 생각해봅시다. class는 this.props로부터 friend.id를 읽어내고 컴포넌트가 마운트된 이후에 친구의 상태를 구독하며 컴포넌트가 마운트를 해제할 때에 구독을 해지합니다.

  componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

그런데 컴포넌트가 화면에 표시되어있는 동안 friend prop이 변한다면 무슨 일이 일어날까요? 컴포넌트는 다른 친구의 온라인 상태를 계속 표시할 것입니다. 버그인 거죠. 또한 마운트 해제가 일어날 동안에는 구독 해지 호출이 다른 친구 ID를 사용하여 메모리 누수나 충돌이 발생할 수도 있습니다.

클래스 컴포넌트에서는 이런 경우들을 다루기 위해 componentDidUpdate를 사용합니다.

  componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentDidUpdate(prevProps) {    // 이전 friend.id에서 구독을 해지합니다.    ChatAPI.unsubscribeFromFriendStatus(      prevProps.friend.id,      this.handleStatusChange    );    // 다음 friend.id를 구독합니다.    ChatAPI.subscribeToFriendStatus(      this.props.friend.id,      this.handleStatusChange    );  }
  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

React 애플리케이션의 흔한 버그 중의 하나가 componentDidUpdate를 제대로 다루지 않는 것입니다.

이번에는 Hook을 사용하는 컴포넌트를 생각해봅시다.

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

이 경우에는 버그에 시달리지 않습니다.(달리 바꾼 것도 없는데 말이죠.)

useEffect가 기본적으로 업데이트를 다루기 때문에 더는 업데이트를 위한 특별한 코드가 필요 없습니다. 다음의 effect를 적용하기 전에 이전의 effect는 정리(clean-up)합니다. 구독과 구독 해지 호출을 반복해서 만들어내는 컴포넌트를 통해 이를 가시화해봅시다.

// { friend: { id: 100 } } state을 사용하여 마운트합니다.
ChatAPI.subscribeToFriendStatus(100, handleStatusChange);     // 첫번째 effect가 작동합니다.

// { friend: { id: 200 } } state로 업데이트합니다.
ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // 이전의 effect를 정리(clean-up)합니다.
ChatAPI.subscribeToFriendStatus(200, handleStatusChange);     // 다음 effect가 작동합니다.

// { friend: { id: 300 } } state로 업데이트합니다.
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // 이전의 effect를 정리(clean-up)합니다.
ChatAPI.subscribeToFriendStatus(300, handleStatusChange);     // 다음 effect가 작동합니다.

// 마운트를 해제합니다.
ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // 마지막 effect를 정리(clean-up)합니다.

이러한 방식으로 동작하는 것이 일관성을 유지해주며 클래스 컴포넌트에서는 흔히 업데이트 로직을 빼먹으면서 발생할 수 있는 버그를 예방합니다.

팁: Effect를 건너뛰어 성능 최적화하기

모든 렌더링 이후에 effect를 정리(clean-up)하거나 적용하는 것이 때때로 성능 저하를 발생시키는 경우도 있습니다. 클래스 컴포넌트의 경우에는 componentDidUpdate에서 prevProps나 prevState와의 비교를 통해 이러한 문제를 해결할 수 있습니다.

componentDidUpdate(prevProps, prevState) {
  if (prevState.count !== this.state.count) {
    document.title = `You clicked ${this.state.count} times`;
  }
}

이러한 요구 조건은 흔하기 때문에 useEffect Hook API에 이미 내재하여 있습니다. 특정 값들이 리렌더링 시에 변경되지 않는다면 React로 하여금 effect를 건너뛰도록 할 수 있습니다. useEffect의 선택적 인수인 두 번째 인수로 배열을 넘기면 됩니다.

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // count가 바뀔 때만 effect를 재실행합니다.

위의 예시에서 우리는 [count]를 두 번째 인수로 넘깁니다. 이것이 의미하는 바는 다음과 같습니다. count가 5이고 컴포넌트가 리렌더링된 이후에도 여전히 count는 변함없이 5라면 React는 이전 렌더링 시의 값 [5]를 그다음 렌더링 때의 [5]와 비교합니다. 배열 내의 모든 값이 같기 때문에(5 === 5) React는 effect를 건너뛰게 됩니다. 이런 식으로 최적화가 가능합니다.

count가 6으로 업데이트된 뒤에 렌더링하면 React는 이전에 렌더링된 값 [5]를 그다음 렌더링 시의 [6]와 비교합니다. 이때 5 !== 6 이기 때문에 React는 effect를 재실행합니다. 배열 내에 여러 개의 값이 있다면 그중의 단 하나만 다를지라도 React는 effect를 재실행합니다.

이것은 정리(clean-up)를 사용하는 effect의 경우에도 동일하게 작용합니다.

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

  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  return () => {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
}, [props.friend.id]); // props.friend.id가 바뀔 때만 재구독합니다.

두 번째 인자는 빌드 시 변환에 의해 자동으로 추가될 수도 있습니다.

주의

이 최적화 방법을 사용한다면 배열이 컴포넌트 범위 내에서 바뀌는 값들과 effect에 의해 사용되는 값들을 모두 포함하는 것을 기억해주세요. 그렇지 않으면 현재 값이 아닌 이전의 렌더링 때의 값을 참고하게 됩니다. 이에 대해서는 함수를 다루는 방법 의존성 배열이 자주 바뀔 때는 어떻게 해야 하는가에서 더 자세히 알아볼 수 있습니다.

effect를 실행하고 이를 정리(clean-up)하는 과정을 (마운트와 마운트 해제 시에)딱 한 번씩만 실행하고 싶다면, 빈 배열([])을 두 번째 인수로 넘기면 됩니다. 이렇게 함으로써 React로 하여금 여러분의 effect가 prop이나 state의 그 어떤 값에도 의존하지 않으며 따라서 재실행되어야 할 필요가 없음을 알게 하는 것입니다. 이는 의존성 배열의 작동 방법을 그대로 따라서 사용하는 것일 뿐이며 특별한 방법인 것은 아닙니다.

빈 배열([])을 넘기게 되면, effect 안의 prop과 state는 초깃값을 유지하게 됩니다. 빈 배열([])을 두 번째 인수로 넘기는 것이 기존에 사용하던 componentDidMount와 componentWillUnmount 모델에 더 가깝지만, effect의 잦은 재실행을 피할 수 있는 더 나은 해결방법이 있습니다. 또한 React는 브라우저가 다 그려질 때까지 useEffect의 실행을 지연하기 때문에 추가적인 작업을 더하는 것이 큰 문제가 되지는 않습니다.

exhaustive-deps 규칙을 eslint-plugin-react-hooks 패키지에 포함하는 것을 추천합니다. 이 패키지는 의존성이 바르지 않게 지정되었을 때 경고하고 수정하도록 알려줍니다.

다음 단계

여기까지 오느라 수고가 많으셨습니다. 긴 글이지만 끝까지 읽으면서 많은 궁금증이 해결되었기를 바랍니다. 지금까지 배운 State Hook과 Effect Hook을 결합하면 많은 것을 할 수 있습니다. class를 이용하는 대부분의 경우를 구현할 수 있습니다. 구현되지 않는 경우에는 additional Hooks이 도움이 될 것입니다.

우리는 동기에 서술되어있는 문제들을 Hook이 어떻게 해결할 수 있는지 알아가고 있습니다. 어떻게 effect의 정리(clean-up)가 componentDidUpdate와 componentWillUnmount에서의 중복을 피하고, 관련 있는 코드들을 한곳에 모이게 하며 버그를 줄일 수 있게 도와주는지 알아보았습니다. 또한 effect를 그 목적에 따라 어떻게 구분하는지도 보았습니다. 이는 클래스 컴포넌트에서는 할 수 없는 일이었죠.

지금 이 시점에서 여러분은 Hook이 어떻게 작동하는 것인지 궁금해하고 있을 것입니다. 어떻게 React는 리렌더링 시에 특정한 useState의 실행이 어떤 state 변수에 상응하는지 아는 것일까요? 어떻게 React는 이전과 그다음 effect를 업데이트 때마다 “매치업하는(일치하는)” 것일까요? 이에 대해서는 다음 장에서 Hook의 작동에 핵심적인 Hook의 규칙을 통해서 배워볼 것입니다

반응형

'개발정보' 카테고리의 다른 글

React(리액트) useMemo  (0) 2022.11.23
React(리액트) Router , 페이지 이동  (0) 2022.11.23
React(리액트) Fragments  (0) 2022.11.23
REACT :: 리액트 설치하기  (0) 2022.11.16
JavaScript :: Var, Let, Const 차이점  (0) 2022.11.16