ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TIL_200905(React Hook 공식문서)
    Today I Learned 2020. 9. 5. 22:55

    왜 React에서 Hook을 사용해야하는가?

    Hook은 Class없이 React 기능들을 사용하는 방법을 알려줍니다.

    그렇다면 왜 Class없이 React를 사용하려 할까요?

     

    • 컴포넌트 사이에서 상태와 관련된 로직을 재사용하기 어렵습니다.
      • Hook은 컴포넌트로부터 상태관련 로직을 추상화할 수 있기 때문에 독립적인 테스트와 재사용이 가능합니다. 
      • Hook은 계층 변화없이 상태관련 로직을 재사용할 수 있도록 도와줍니다.

     

    • 복잡한 컴포넌트들은 이해하기 어렵습니다.
      • 생명주기 메서드에 자주 관련없는 로직이 섞여서 이로 인해 버그가 쉽게 발생하고 무결성을 쉽게 해칩니다.
      • componentDidMount와 componentDidUpdate로 데이터를 가져오는 것
      • componentWillUnmount로 cleanup을 수행하는것

     

    • Class는 사람과 기계를 혼동시킵니다.
      • Javascript에서 this가 작동되는 방법, 이벤트 핸들러가 등록되는 방법들을 기억해야만 합니다.

     

    State Hook

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

     

    useState라는 Hook을 사용하여 카운터를 구현하는 예시입니다. useState는 현재의 state 값(count)과 값을 업데이트하는 함수(setCount)를 제공합니다. 지금까지 Functional 컴포넌트를 작성하다가 state 값이 필요하다면 Class 컴포넌트로 바꾸었습니다. 이제는 useState를 사용하여 state 값을 사용할 수 있습니다. useState는 Class의 this.setState와 유사하지만 다릅니다.

     

    Hook 사용규칙

    1. 최상위에서만 Hooks를 호출해야 합니다. 반복문, 조건문, 중첩된 함수 내에서 Hook을 실행하지 마세요.
    2. React 함수 컴포넌트 내에서만 Hook을 호출해야합니다. 일반 Javascript 함수에서는 Hook을 호출하지 마세요.

    만약에 상태 관련 로직을 컴포넌트간에 재사용하고 싶은 경우가 생긴다면 두가지 방법을 사용할 수 있습니다.

    1. higher-order components
    2. render props

    로직을 useFriendStatus라는 custom Hook으로 뽑아냅니다.

    import React, { useState, useEffect } from 'react';
    
    function useFriendStatus(friendID) {
      const [isOnline, setIsOnline] = useState(null);
    
      function handleStatusChange(status) {
        setIsOnline(status.isOnline);
      }
    
      useEffect(() => {
        ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
        return () => {
          ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
        };
      });
    
      return isOnline;
    }
    function FriendStatus(props) {
      const isOnline = useFriendStatus(props.friend.id);
    
      if (isOnline === null) {
        return 'Loading...';
      }
      return isOnline ? 'Online' : 'Offline';
    }

     

    각 컴포넌트의 state는 완전히 독립된 state를 가집니다. 그래서 상태 관련 로직을 재사용하고 한 컴포넌트 안에서 같은 custom Hook을 두번 사용할 수도 있습니다.

     

     

     

    useState

    초기값이 0 인 count 라는 변수가 선언해봅니다. 

    import React, { useState } from 'react';
    
    function Example() {
    	// 새로운 state 변수인 count를 선언합니다.
        const [count, setCount] = useState(0)

     

    state를 사용하는 방법은 간단합니다.

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

     

    count 변수의 값을 바꾸려면 setCount를 호출하면 됩니다.

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

     

     

    useEffect

    useEffect를 사용하여 함수 컴포넌트에서 side effect를 수행할 수 있습니다. 데이터 가져오기, 구독 설정하기, 수동으로 컴포넌트의 DOM을 수정하는 것들이 side effect입니다. 

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

     

    이전의 카운터 예시와 합친다면 이렇게 됩니다.

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

     

    • useEffect는 리액트가 컴포넌트를 렌더링한 이후에 수행할 것을 기억하도록 합니다.
    • useEffect가 컴포넌트 안에서 불리는 이유는 컴포넌트 내부에서 state 변수에 접근하기 위해서 입니다.
    • useEffect는 첫번째 렌더링과 이후의 모든 업데이트 이후에 수행됩니다.

     

    두 종류의 side effect

    1. Clean-up이 필요한 경우
    2. Clean-up이 필요하지 않은 경우

     

    Clean-up을 이용하는 Effects

    친구의 온라인 상태를 구독할 수 있는 ChatAPI 모듈의 예입니다. 

    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는 정리를 위한 함수를 반환할 수 있기 때문에 구독의 추가와 제거를 위한 로직을 묶어둘 수 있습니다. React가 clean-up하는 시점은 렌더링이 실행되는 때마다 입니다. 다음 차례의 effect를 실행하기 전에 이전의 렌더링에서 파생된 effect가 정리되는 것입니다. 

     

    모든 리렌더링 시에 clean-up이 실행되는 이유는 업데이트 때문입니다. 마운트가 해제되기 전에 친구의 아이디가 바뀌는 경우가 있을 것입니다. 그렇다면 마운트를 해제하는 과정에서 충돌이 일어날 수 있습니다. 이럴때를 대비해서 class 컴포넌트는 componentDidUpdate를 추가합니다. 하지만 Hook을 사용한다면 달리 바꾸지 않아도 됩니다.

     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
        );
      }
    useEffect(() => {
      // ...
      ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
      return () => {
        ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
      };
    });

     

    그러나 매번 effect를 정리하거나 적용하는 것이 성능 저하를 발생시키기도 합니다. 이때는 effect를 건너뛸 수 있습니다. 두번째 인수로 배열을 넘겨주고 특정 값이 변하지 않는다면 effect를 건너뛰게 됩니다.

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

     

     

    'Today I Learned' 카테고리의 다른 글

    TIL_200907(PixiJS - sprite)  (0) 2020.09.07
    TIL_200906(React Hook 공식문서)  (0) 2020.09.06
    TIL_200622  (0) 2020.06.22
    TIL_200619  (0) 2020.06.20
    TIL_200618  (0) 2020.06.18

    댓글

Designed by CHANUL