[React] Hook에 대하여!

Lemon·2022년 9월 12일
0

React

목록 보기
12/21
post-thumbnail

React에 hook이 나오기 전까진 리액트 라이프 사이클을 사용하기 위해서 Class를 사용해야 됐었습니다. 리액트를 Class로 사용하기 위해서 React.Componentexpend해야 하고 state를 사용하려면 super(props)도 적어줘야합니다. 대부분은 이게 왜 필요한지 모르고 적고 있었습니다. 사람들은 이런 부분은 자동으로 되면 좋을 것 같다는 생각을 하게 됩니다. 그리고 Class를 사용해서 재사용 가능한 로직을 만들려면 HOC(High-Order-Component)를 사용해서 만들어야 되는데 나중에는 HOC 헬을 겪게 되고 코드도 이해하기 힘들어집니다. state를 정의하고 업데이트 하는데도 많은 코드가 필요했습니다. 이런 것들을 해결하기 위해 react hook이 나오게 됩니다.


React Hook이란?

💡 Hook은 함수 컴포넌트에서 React state와 생명주기 기능(lifecycle features)을 "연동(hook into)" 할 수 있게 해주는 함수입니다.
- React 공식 문서 -

React 16.8v부터 추가된 기능으로 Class 형식의 리액트 컴포넌트에서만 할 수 있었던 state 관리 및 라이프 사이클 등을 함수형 컴포넌트에서도 사용할 수 있게 만들어주는 새로운 기능입니다.

  • Hook은 class 안에서는 동작하지 않습니다.
  • 대신 class 없이도 React를 사용할 수 있게 해주는 것입니다.
  • React는 useState 같은 내장 Hook을 몇가지 제공합니다.
  • 컴포넌트 간에 상태 관련 로직을 사용하기 위해 Hook을 직접 만드는 것(custom hook)도 가능합니다.

React Hook이 필요한 이유

React Hooks는 주로 Class Component로 사용되어온 React에서 느껴왔던 불편함이나 문제점들을 해결하기 위해 개발되었습니다.

React는 주로 Class Component를 사용하고 React Hooks는 Functional Component를 사용합니다. 둘의 차이점은 아래의 이미지와 같습니다.

React Hooks가 나오기 전에는 생명주기를 함수형 컴포넌트에서는 사용하지 못했기 때문에 함수형 컴포넌트가 더 간결하고 빠르더라도 클래스형 컴포넌트를 써왔습니다.


Class 컴포넌트에 대한 불만 사항

1. extends와 super(props)를 매번 작성해야되나?

클래스형 리액트 컴포넌트 사용을 위해서는 extends 키워드를 사용해 React.Component를 상속 받아야합니다. 또한 리액트 컴포넌트를 만들고 state를 사용하기 위해서는 custructor 내부 최 상단에 super(props)를 해줘야 합니다. React.Component에도 props을 넘겨주기 위한 것이죠. 하지만 항상 똑같이 해야 되는 걸 우리가 할 필요가 있나?라는 의문이 들게 하는 코드입니다.


2. this가 뭔지 많은 사람들이 헷갈려 한다.

자바스크립트를 공부하다 보면 this가 나오는데 클래스를 사용하다 보면 많이 보입니다.

class Foo extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    console.log('Click happened');
  }
  render() {
    return <button onClick={this.handleClick}>Click Me</button>;
  }
}

위의 코드에서 보면 알 수 있듯이 thisbind해야 class관련 함수와 props에 접근할 수 있습니다. 대부분 bind를 해줘야 하고, 하는 것과 안하는 것에 큰 퍼포먼스 차이가 없다면 그냥 자동으로 해주면 안될까하는 의견이 나옵니다.


3. 고차 컴포넌트(HOC, Higher Order Component) 지옥

리액트를 쓰다 보면 자주 쓰는 로직을 따로 빼내서 사용할 필요가 있는데, 그 전까지는 HOC를 사용했습니다.

HOC (Higher Order Component)란?
화면에서 재사용 가능한 로직만을 분리해서 component로 만들고, 재사용 불가능한 UI와 같은 다른 부분들은 parameter로 받아서 처리하는 방법입니다.

위의 예제는 api를 통해 user의 list를 가져와서 A 페이지와 B 페이지 모두 한테 뿌려주고 싶은 것입니다. user list를 뿌려주려면 componentDidMount로 컴포넌트가 시작했을 때 user list를 가져와 줘야합니다. 이 부분이 어떤 컴포넌트에서든 중복되게 사용하게 됩니다. 이러한 중복되는 부분을 없애기 위해 HOC를 사용했습니다.

user list를 가져오는 공통적인 부분은 HOC 컴포넌트에 넣어주고,
그 HOC 컴포넌트로 각각의 컴포넌트를 감싸주면 모든 컴포넌트에 따로 인증을 위한 부분은 넣어주지 않아도 됩니다.
Hooks가 나오기 전에는 이 방법이 추천되는 방법이었습니다. 하지만 여기서도 문제가 있습니다. 바로 너무 많은 Wrapper 컴포넌트가 생길 수 있다는 겁니다.
아래의 코드처럼 Wrapper가 너무 많아지면 데이터 흐름을 파악하기가 힘들어집니다.

const HocHell = () => {
  return (
    <Hoc1>
      <WithMousePosition>
        <WithWindowSize>
          <WithUserLocation>
            <SomeComponent />
          </WithUserLocation>
        </WithWindowSize>
      </WithMousePosition>
    </Hoc1>
  );
};

그런 것이 여러 개가 겹치다보면 HOC 헬을 경험할 수 있습니다.
만약 SomeComponent가 마우스 포지션, window 크기, 유저 위치 등의 정보가 필요하다면 아래와 같이 HOC로 감싸줄 수 있습니다. 이런 구조는 가독성도 좋지않고 element를 찾기 힘들어집니다.
이러한 문제는 Custom React Hooks를 이용해서 해결할수가 있습니다.

Custom React Hooks을 사용해서 SomeComponentrender해주면 아래와 같이 사용이 가능합니다.

const WithHook = () => {
  const mousePosition = useMousePosition()
  const windowSizes = useWindowSize()
  const userLocation = useUserLocation()
  
  return (
    <SomeComponent
      mousePosition={mousePosition}
      windowSizes={windowSizes}
      userLocation={useLocation}
    >
  )
}

클래스형 컴포넌트와 Hook이 등장하기 전 함수형 컴포넌트에서 사용하던 HOC는 마우스 포지션(WithMousePosition) 하위의 window의 크기(WithWindowSize) 하위의 유저 위치 정보(WithUserLocation) 하위에 SomeComponent가 존재합니다.

즉, 컴포넌트의 차원이 불필요하게 높아집니다.

하지만 Hook을 사용하면 상위 컴포넌트에서 해당 3가지 기능의 모듈을 불러와 SomeComponent로 전달하기 때문에 두 단계의 차원으로 해결이 가능해집니다.

(보통 Hook은 use를 붙여서 이름을 지어줍니다.)


Class vs Hook 비교해보기

Class 형 State 업데이트 vs Hook을 사용한 State 업데이트

Home Componenet는 컴포넌트가 마운트 됐을 때, pathName prop이 변경됐을 때 lookups 데이터를 api에서 받아와야 하는 코드를 class형으로 작성하면 아래와 같습니다.

class Home extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      lookups: null
    };
    // this를 사용하기 위해서 아래와 같이 바이드를 해줍니다.
    this.fetchLookups = this.fetchLookups.bind(this);
  }

  componentDidMount() {
    this.fetchLookups(this.props.pathName);
  }
  componentDidUpdate(prevProps) {
    // pathName prop이 변경 되었을 때
    if (prevProps.pathName !== this.props.pathName) {
      this.fetchLookups(this.props.pathName);
    }
  }
  fetchLookups(pathName) {
    fetchApi({ url: `/lookup?url=${pathName}` }).then((lookups) => {
  	  // lookups 데이터가 반환 되면 state업데이트
      this.setState({
        lookups
      });
    });
  }
  render() {
    if (!this.state.lookups) return null;
    return <pre>{JSON.stringify(this.state.lookups)}</pre>;
  }
}

위의 코드를 Hook을 사용했을 때는 아래와 같습니다.

const HomeWithHook = ({ pathName }) => {
  const [lookups, updateLookups] = useState(null);
  useEffect(() => {
    if (pathName) {
      fetchApi({ url: `/lookup?url=${pathName}` }).then((lookups) => {
        updateLookups(lookups);
      });
    }
  // dependency를 pathName으로 해주면 맨 처음에 한번 pathName이 변경 되었을 때 또 실행
  }, [pathName]);
  if (!lookups) return null;
  return <pre>{JSON.stringify(lookups)}</pre>;
};

훨씬 간결해지고 클래스에서 사용한 반복적이고 불필요한 느낌의 코드가 없어집니다.


class 생명 주기 vs Hook 생명 주기

왼쪽 코드와 오른쪽 코드를 보면 선명하게 코드가 간결해 진 것을 볼 수 있습니다.
그 이유는 Class Component에서는 생명주기를 이용할 때 ComponentDidMountCompoenentDidUpdate, compoenentWillUnmount 이렇게 다르게 처리해주지만 리액트 Hook을 사용할 때는 useEffect 안에서 다 처리를 해줄 수 있기 때문입니다.


Hook 사용 규칙

💡 아래의 규칙을 지켜야 리액트가 훅의 상태를 제대로 기억할 수 있습니다.

1. 최상위에서만 Hook을 호출해야 한다.

반복문, 조건문, 중첩된 함수 내에서는 Hook을 실행할 수 없습니다.

import React, { useState } from "react"

function Hooks(props) {
  if (!props.isExist) {
    const [state, setState] = useState(); // Error!
  }

  const [state2, setState2] = useState(); // Error!
}

react가 여러 훅들을 구분할 수 있는 유일한 정보는 훅이 사용된 순서 뿐이기 때문입니다.


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

일반 JavaScript 함수에서는 Hook을 호출해서는 안 됩니다.
Hook을 호출할 수 있는 곳이 딱 한 군데 더 있습니다. 바로 직접 작성한 custom Hook 내입니다. 이것에 대해서는 나중에 알아보겠습니다.


React Hook의 장점

  • 간결한 코드
  • 가독성 향상
  • 많은 라이브러리들도 훅으로 나오고 있음
  • HOC 헬을 벗어날 수 있음
  • 불필요한 보일러플레이트 코드 제거

결론

이제는 클래스 컴포넌트를 사용해서 컴포넌트를 만들 이유가 많이 없어졌습니다. Hook이 써야되는 코드도 많이 줄어들게 되고 보고 읽기도 편하기 때문입니다.


🔗 참고 링크
https://doqtqu.tistory.com/340?category=804293
https://surviveasdev.tistory.com/entry/React-hook이-나온-이유와-사용해야-하는-이유
https://surviveasdev.tistory.com/entry/리액트-훅React-hook-사용-방법-예제로-알아보기?category=1205973
https://study.wecode.co.kr/session/content/196
https://ko.reactjs.org/docs/hooks-intro.html#gatsby-focus-wrapper
https://www.youtube.com/watch?v=C26vJqelKlA&feature=share

profile
프론트엔드 개발자 가보자고~!!

0개의 댓글