개발/React

[React] React Hooks의 장단점

개발 여행 2021. 12. 27. 02:53
728x90

React Hooks는 ReactConf 2018에서 발표된 새로운 기능으로 16.8 버전부터 새로 추가되었다. Hooks를 이용하면 class 없이 state와 여러 React의 기능을 사용할 수 있다.

 

Hooks는 왜 필요한가?

Hooks의 등장 배경은 기존 Class Component를 사용할 떄의 불편함과 문제점을 해결하기 위해 개발되었다고 한다.

 

1. Reusing logic (컴포넌트 간 상태가 있는 logic의 재사용성이 떨어짐)

Class component 기반의 React 코드를 작성할 때 어플리케이션을 구성하는 여러 컴포넌트의 재사용성을 위해 Higher-Order ConmonentsRender props와 같은 패턴을 통해 문제를 해결하였다고 한다. 두 가지 패턴은 특정 상황에 적절하게 사용될 수 있지만,  구조적 제약이 생길 수 있고, 로직의 재활용을 위해 엄청난 양의 Wrapper Component가 생겨서 Wrapper Hell을 초래한다.

 

Higher Order Component란?

간단하게 정리하면, 화면에 재사용 가능한 로직을 분리하여 컴포넌트로 만들고, 재사용 불가능한 부분은 (ex. UI) 파라미터로 받아서 처리하는 방법이다.

 

 

2. Giant component (복잡한 컴포넌트는 이해하기 어려움)

각각의 라이프 사이클 메서드에 관련 없는 로직이 혼합되어 있을 수 있다. componentDidMount 와 componentDidUpdate는 컴포넌트 안에서 데이터를 가져오는 작업을 수행할 때 사용되어야 하지만, componentDidMount에서 이벤트 리스너를 설정하는 것과 같은 관계없는 로직이 포함되기도 하며, componentWillUnmount에서 cleanup 로직을 수행하기도 할 것이다.  이것은 버그를 발생시키고 무결성을 해칠 수 있다. 또한 각 라이프사이클에서 수행할 로직이 많아지면 많아질수록 어떤 것을 cleanup 해줘야 하는지, 수정해야 하는지 찾기 어려울 것이다. 

 

 

3. Confusing classes (클래스는 개발자와 컴퓨터 모두에게 혼란을 줄 수 있다.)

class를 사용하기 위해서는 this 키워드가 어떻게 작동하는지 정확히 알아야한다. this키워드는 함수 선언 시점에 결정되는 것이 아니라, 함수 실행 시점에 결정되는 값이므로 함수가 어떻게 실행되는지에 따라 값이 변경될 수 있다. 사이즈가 큰 프로젝트에서는 this를 예상하고 추적하는 것에 작은 실수가 발생할 수 있다. 또한, class 사용 시 컴파일 시점에서 모든 것이 정확히 일치하는 것이 어렵다. 메서드가 사용되지 않지만 해당 메서드는 익명의 메서드로 남아있을 수 있다. 컴파일 시점에서 정확히 판단할 수 없기 때문이다. class는 최적화하기 어려운 코드를 생성한다.

 


Hooks의 장점

 

1. 더 빠른 성능과 짧은 코드 양

리액트에서 작성한 코드를 실행할 때 바벨을 이용해서 코드를 컴파일한 후 실행시키는데,  클래스형 컴포넌트일 경우 코드의 양이 길어진다. (바벨을 사용하여 컴파일한 코드는 babal 홈페이지에서 실행해볼 수 있다.)

 

또한, 클래스형 컴포넌트에서는 라이프 사이클을 이용할 때 componentDidMount, componentDidUpdate, componentWillUnmount 모두 다르게 처리하지만, react hook을 사용하면 useEffect 안에서 모두 처리 가능하다.

예시 코드)

// class
componentDidMount() {
	this.updateList(this.props.id);
}

componentDidUpdate(prevProps) {
	if (prevProps.id !== this.props.id) {
    	this.updateList(this.props.id);
    }
}

updateList = () => {
	feachList(id).then((list) => this.setState({list}));
}

// hooks
useEffect(() => {
	feachList(id).then((res) => setRes(res));
}, [id]);

 

2. Weapper 컴포넌트양 감소

HOC 컴포넌트를 Custom React Hooks로 대체하면, Weapper 컴포넌트를 사용하지 않아도 간단하게 구현할 수 있다.

 

만약 공통적으로 적용애햐 하는 코드가 있다(언어, 테마, 인증 설정 등) 아래와 같이 감싸서 작성해야 한다. 이럴 경우 Wrapper 컴포넌트의 양이 엄청나게 증가한다면, 데이터 흐름을 파악하기 어렵다.

<LanguageHOC>
  <ThemeHOC>
    <AuthHOC>
      <MyPage />
    </AuthHOC>
  </ThemeHOC>
</LanguageHOC>

 

반면 hook을 사용하면 데이터를 내려줄 때 어떤 페이지이든 감싸지 않아도 된다.

function useAuth() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
  	fetchUsers().then(users => setUsers(users));
  }, []);

  return [users];
}

function MyPage() {
  const [users] = useAuth();

  return (
    <div>
      My Page
      {users.map(({ name, url }) => (
        <div key={name}>
          <h3>{name}, {url}</h3>
        </div>
      ))}
    </div>
  );
}

 

Hooks의 단점

1. 호출되는 순서에 의존

hooks는 반복문, 조건문, 중첩된 함수 내에서 호출할 수 없다.

만약 클릭하면 페이징되는 버튼을 구현하기 위해 useFetch를 사용하려고 한다면, react-async-hook 라이브러리를 사용하거나 custom hook을 만들어서 해결해야 한다. hooks의 규칙을 따르기 위해 많은 리소스가 필요하고, 코드가 많아지면 복잡성이 증가한다.

 

2. useEffect의 빈틈

useEffect는 두 번째 인수로 dependency list를 받고, 하나의 값이라도 변경됐을 때 useEffect 내부의 로직을 실행하여 라이프 사이클을 흉내 낸다. React에서 hooks' dependency array의 변경을 감지하기 위해 Object.is() 메서드를 사용하는데 대체로 원시 타입에 올바르게 작동한다고 한다. 참조형 데이터를 비교할 때는 렌더링 될 때마다 참조 메모리 주소가 다르기 때문에 매번 실행될 것이다. useMemo를 사용하여 동일한 객체가 전달될 수 있도록 처리할 수도 있지만, 개발자가 놓치고 넘어가기 쉬운 포인트인 것 같다.

 

3. hooks는 클로저에 의존적이다.

프로그램이 커지면 커질수록 hooks가 많아지는데, 클로저는 복잡성을 증가시킨다. 최신상태가 아닌 클로저는 해결하기 어렵다. 

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

  useEffect(function() {
    setInterval(function log() {
      console.log(`Count is: ${count}`);
    }, 2000);
  }, []);

  return (
    <div>
      {count}
      <button onClick={() => setCount(count + 1) }>
        Increase
      </button>
    </div>
  );
}

위 코드에서 출력된 count는 항상 0 이다. console.log()는 생성될 때의 count (0)을 캡처하여 기억하고 있으므로, 계속 count는 0일 것이다. 물론 해결하기 위해 아래와 같이 변경할 수 있다. 하지만 클로저 역시 예상하기 어렵다.

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

  useEffect(
    function () {
      const id = setInterval(function log() {
        console.log(`Count is: ${count}`);
      }, 2000);
      return function () {
        clearInterval(id);
      };
    [count]
  );

  return (
    <div>
      {count}
      <button onClick={() => setCount(count + 1)}>Increase</button>
    </div>
  );
}

 

 

 

 

 

참고

React Conf 2018

https://youtu.be/7umCiutiJ7M

천종희 기술 블로그

https://ui.toast.com/weekly-pick/ko_20200922

https://hewonjeong.github.io/deep-dive-how-do-react-hooks-really-work-ko/

728x90