React - 공식문서 읽어보기4

Evan Lee ㅣ 2022. 7. 17. 03:53

Hook

- 리액트 16.8버전부터 추가됬고, 기존 Class 바탕의 코드를 작성할 필요없이 상태값과 여러 다른 기능을 사용할 수 있게 되었습니다. 
- Class의 단점 보완과 재사용성을 강화하기 위해 만들어졌다. 

 

Hook의 사용 규칙 

최상위에서만 Hook을 호출해야한다. 반복문이나 조건문, 중첩 함수 내 Hook을 실행시 기대치 못한 사이드 이펙트 있을수 있다. 
React 함수 컴포넌트 혹은 Custome Hook 내에서만 호출 할 수 있습니다. 

 


State Hook

state 변수 선언하기

 

클래스를 사용할때, constructor 안 this.state를 { count: 0 }으로 상태 변수를 선언하는 코드다.

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

 

useState Hook을 사용한 상태 변수를 선언하는 코드다.

import React, { useState } from 'react';

function Example() {
  // 새로운 state 변수를 선언하고, 이것을 count라 부르겠습니다.
  const [count, setCount] = useState(0);

 

둘이 차이를 알겠나요?

1. 함수 컴포넌트는 this를 가질 수 없기 때문에, useState라는 Hook을 직접 컴포넌트에 호출하여 변수를 선언합니다.

2. 저기서 useState가 인자로 넘겨주는 값은 state의 초기값 입니다.

3. state는 객체일 필요가 없고, 사용자가 원하는 타입을 가질 수 있습니다. 

 

useState Hook 기본 코드 해석해보기

1:  import React, { useState } from 'react';
 2:
 3:  function Example() {
 4:    const [count, setCount] = useState(0);
 5:
 6:    return (
 7:      <div>
 8:        <p>You clicked {count} times</p>
 9:        <button onClick={() => setCount(count + 1)}>
10:         Click me
11:        </button>
12:      </div>
13:    );
14:  }
  • 1번 라인 : React에서 useState를 가져옵니다. 
  • 4번 라인 : Hook을 이용하여 state 변수와 갱신 함수 선언을 해주고, 초기값으로 0을 넣어줍니다. 
  • 9번 라인 : 버튼을 클릭시 state 갱신 함수를 호출하여, count 변수에 1을 더한 값을 컴포넌트에 넘기고 재랜더링 합니다. 

 


Effect Hook 

데이터 가져오기, 구독 설정하기, 수동으로 React 컴포넌트의 DOM을 수정하는 것까지 다 Side Effect입니다. 

 

Side Effect 코드 예시 

 React가 DOM을 바꾸고 난뒤 업데이트하는 componentDidMount와 componentDidUpdate를 통한 side effect 코드

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

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

이렇게 하면 클래스안처럼 두 개의 생명주기 (componentDidMount componentDidUpdate)안에 있는 같은 코드가 중복되것을 useEffect Hook을 통해 불필요한 코드 중복을 없애고 기능을 구현한 것입니다. 

 

useEffect Hook을 이용하면 우리는 React에 컴포넌트가 렌더링 이후에 어떤 일을 수행할지 말해주는데, 우리가 넘긴 함수를 기억한 뒤 DOM 업데이트를 수행 하고 호출합니다. 

 

useEffect를 컴포넌트 안에서 불러내는 이유는 내부안에 상태변수에 접근을 하기 위함이고, 그 컴포넌트 안에 모든 상태 변수에 접근이 가능합니다.

 

정리(clean-up)를 이용한 Effects

 

위에 예시코드는 정리(clean-up)이 필요하지 않은 Side Effect였습니다. 하지만 정리가 필요한 코드도 있겠죠 ? 예를 들면 외부 데이터에 구독을 설정해야하는 경우를 생각해보겠습니다. 그리고 이것을 클래스와 훅 코드로 비교하겠습니다. 

 

클래스를 이용한 상태를 구독하고 보여주는 코드

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';
  }
}
여기서 보면 생명주기 메소드 안에 두 개의 메소드가 같은 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 하나에 구성이되어 작성할 수 있습니다. React는 컴포넌트가 마운트 해제되는 때 clearnup()을 실행하는데, effect는 렌더링이 실행될때마다 실행이  되기때문에 이전의 렌더링에서 파생된 effect를 정리해야하기 때문입니다. 

 


Custom Hook

컴포넌트 로직을 함수로 재사용할 수 있게 자신만의 Hook을 만드는 것 
예를 들면 앞서 봤던 useState와 useEffect를 하나의 Custom Hook으로 묶어 사용 할 수 있겠죠 ?

FriendListItem 과 FriendStatus 컴포넌트에 안에 있는 공통 로직

 

 

FriendListItem Component
FriendStatus Component

 

위 두 컴포넌트 사이에 정확히 똑같은 로직이 한번씩 사용되는 것을 확인할 수 있는데요, 여기서 Custom Hook으로 추출하여 사용해 볼 수 있을 것 같네요. 

useFriendStatus Custom Hook

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

위에 해당 로직을 useFreindStatus 라는 Custom Hook으로 만듭니다. 

 

FriendStatus, FriendListItem Custom 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>
  );
}

useReducer

useState의 대체 함수인데, 향후 상태관리에 사용할 Redux와 동작원리가 비슷하기때문에 알아두면 좋을 것 같다. 
다수의 하윗값을 포함한 복잡한 정적 로직을 만드는 경우 useReducer를 더 선호합니다. 

 

useState를 이용한 간단한 Count 로직 

import React, {useState} from 'react'

export default function State() {
  const initialCount = 0;
  const [count, setCount] = useState(initialCount);
  return (
    <div>
      Count : {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prev => prev +1)}>Increment</button>
      <button onClick={() => setCount(prev => prev -1)}>Decrement</button>
    </div>
  )
}

useReducer를 이용한 Count 로직

import React, {useReducer} from 'react'

export default function State() {
  const initialState = {count : 0};

  function reducer(state, action){
    switch(action.type){
      case 'reset':
          return initialState;
      case 'increment':
          return { count: state.count + 1};
      case 'decrement':
            return { count: state.count - 1};
      default: 
          throw new Error();
    }
  }

  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <div>
      Count(reducer) : {state.count}
      <button onClick={() => dispatch({type:'reset'})}>Reset</button>
      <button onClick={() => dispatch({type:'increment'})}>Increment</button>
      <button onClick={() => dispatch({type:'decrement'})}>Decrement</button>
    </div>
  )
}

일단 Reducer를 보고 나고 든 생각은 switch 조건문 처럼 사용하는게 눈에 들어왔다. 그리고 둘이 완벽하게 같은 동작을 구현하고 있다. 얼핏보면 useReducer가 좀 더 복잡해보일 수 있으나 이 로직같은 경우는 하윗값이 하나뿐이고 간단하기때문에 그렇지만, 상태관리하고 있는게 count뿐만아니라 해당 Reset, increment, Decrement라는 action에 대해서 바꿔줘야할 하윗값들이 많아지면 useReducer가 useState보다 더 좋은 선택일 수 있다. 

 

 

https://ko.reactjs.org/docs/hooks-intro.html

 

Hook의 개요 – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

'Study > React' 카테고리의 다른 글

setState의 비동기성을 들어보셨습니까 ?  (0) 2022.12.10
Axios  (0) 2022.09.18
React - 공식문서 읽어보기3  (0) 2022.07.14
React - 공식문서 읽어보기2  (0) 2022.07.13
React - 공식문서 읽어보기  (0) 2022.07.13