React 16.8부터 도입된 hooks는 state를 비롯한 여러 리액트의 특징을 class없이 사용할 수 있도록 도와줍니다. 이번 시간에는 리액트팀이 왜 hools를 도입했고 어떤 도움을 주는지 살펴보겠습니다.

No Breaking Changes

hooks를 시작하기 전, 먼저 살펴볼 특징을 알아보겠습니다.

  • 선택적 사용 - 기존의 코드를 다시 작성할 필요 없이 일부의 컴포넌트들 안에서 Hook을 사용할 수 있습니다. 그러나 당장 Hook이 필요 없다면, Hook을 사용할 필요는 없습니다.
  • 100% 이전 버전과의 호환성 - Hook은 호환성을 깨뜨리는 변화가 없습니다.
  • 현재 사용 가능 - Hook은 배포 v16.8.0에서 사용할 수 있습니다.
  • React에서 class를 제거할 계획은 없습니다.
  • Hook은 알고 있는 리액트 개념을 대체하지 않습니다. - 대신 hooks는 props, state, context, refs, lifecycle 처럼 이미 알고 있는 리액트 개념들에 대해 직관적인 API를 제공합니다. 또한 hooks는 이런 개념을 엮기 위한 강력한 방법을 제공합니다.

Motivation

hooks는 리액트 팀이 5년간 수 많은 컴포넌트를 작성하고 유지하며 겪어온 수 많은 문제를 해결했습니다. 리액트를 비롯한 컴포넌트 모델을 사용해본 많은 사람들은 이와 같은 문제들에 공감할 것입니다.

1. hard to reuse stateful logic between components

리액트는 컴포넌트간에 재사용 가능한 로직을 붙이는 방법을 제공하지 않습니다(store connecting 등). 이러한 문제를 해결하기 위해 리액트 사용자는 보통 render props, higher-order components(HOC) 등을 이용하죠.

하지만 이런 방법들은 컴포넌트의 재구성을 강요합니다. 이런 작업은 성가시고 코드를 추적하기 어렵게 만듭니다(리액트 팀도 성가시다고 생각하는군요..). 리액트 개발자도구에서 어플리케이션을 본다면 컴포넌트가 providers, consumers, HOC, render props, 다른 추상화 등에 대한 레이어로 둘러 쌓인 "wrapper hell"을 볼 수 있게 됩니다.

개발자 도구에서 걸러낼 수 있지만, 이런 현상의 심층에는 다음과 같은 문제점이 있었습니다 => 리액트는 상태관련 로직을 공유하기 위해 더 나은 기초요소(primitive)가 필요하다.

Hooks를 사용하면 컴포넌트로부터 상태관련 로직을 추출해 낼 수 있게 됩니다. 따라서 독립적으로 테스트할 수 있고, 재사용도 가능해지죠. Hooks는 컴포넌트의 계층 변화 없이 상태와 관련된 로직을 재사용 할 수 있게 도와줍니다. 이는 많은 컴포넌트나 커뮤니티간에 hooks를 공유하기 더 쉽게 만들어줍니다.

2. Complex components become hard to understand

간단한 컴포넌트로 시작했지만, 샐 수 없이 많은 상태관련 로직과 부수 효과를 가지게 된 거대하게 변한 컴포넌트는 유지하기 매우 어렵습니다. 각 생명주기 메소드에는 자주 관련 없는 로직들이 섞여있죠. 예시로 componentDidMount 와 componentDidUpdate는 컴포넌트안에서 데이터를 가져오는 작업을 수행할 때 사용 되어야 하지만, 같은 componentDidMount에서 이벤트 리스너를 설정하는 것과 같은 관계없는 로직이 포함되기도 하며, componentWillUnmount에서 cleanup 로직을 수행하기도 합니다. 함께 변화하는 관련있는 코드들은 분리되어지지만, 완벽하게 상관 없는 코드들은 결국 한 메소드 안에 섞여 있게 됩니다. 이러한 상태는 에러가 쉽게 일어나고 무결성을 쉽게 해치게 만들죠.

상태 관련 로직이 광범위하게 퍼져 있기 때문에 많은 경우 이러한 컴포넌트를 작은 컴포넌트들로 분해하는 것은 불가능한 경우가 많습니다. 또한 테스트하기도 매우 어렵죠. 많은 사람들이 리액트와 분리된 상태 관련 라이브러리를 리액트와 함께 쓰는 이유이기도 합니다. 그러나, 이런 상태 관리 라이브러리는 종종 너무 많은 추상화를 사용하고, 서로 다른 파일들 사이에서 건너뛰기를 요구하며 컴포넌트 재사용을 더욱더 어렵게 만들기도 하죠.

이러한 문제를 해결하기 위해 생명주기 메소드를 기반으로 코드를 나누기 보단, Hooks를 사용해 서로 관련 있는 코드 조각을 기준으로 하나의 컴포넌트를 작은 함수들로 나누는 방법을 사용할 수 있습니다(구독 설정이나 데이터를 불러오는 일 등으로 말이죠). 또한 이러한 로직의 추적을 쉽게 할 수 있도록 리듀서를 활용해 컴포넌트의 지역 상태 값을 관리하도록 할 수 있습니다.

3. Class confuse both people and machines

위의 문제들과 더해 코드의 재사용과 조직을 더욱 어렵게 만드는 문제는 컴포넌트가 리액트를 배우는데 큰 걸림돌이라는 것입니다. 사용자는 js에서 this키워드가 어떻게 동작하는지 알아야 합니다. 이 키워드는 다른 언어에서 작동하는 방식과 매우 다르죠. 또한 사용자들은 이벤트 핸들러를 바인딩하는 것을 정확히 알아야 하였으며, 이는 특정 문법 요소들을 사용하지 않는 한 코드를 매우 장황하게 만듭니다. 사용자들은 props, state, top-down data flow를 완벽하게 이해할 수 있지만 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은 명령형 코드로 해결책을 찾을 수 있게 해주며 복잡한 함수형 또는 반응형 프로그래밍 기술을 배우도록 요구하지 않습니다.

Gradual Adoption Strategy

There are no plans to remove classes from React

class를 당장 리액트로 부터 제거할 계획은 없습니다. 또한 리액트 hooks는 기존의 코드들과 100%호환이 가능하죠. 따라서 이미 사용중인 복잡한 class 컴포넌트들을 무리하게 바꾸거나 크게 리팩토링 할 필요는 없습니다. 경험을 토대로 팀원들이 익숙해 질 때 까지 점진적으로 쉽고 중요성이 덜한 컴포넌트부터 차근차근 바꿔 나가는 것을 추천합니다.

React의 개발자들은 현재 사용중인 Class 사례를 Hook으로 교체하는 것을 염두에 두고는 있지만, 미래에도 계속 Class 컴포넌트들을 지원할 예정입니다. 페이스북은 수만 개의 Class 컴포넌트들을 작성했지만 이를 재작성할 계획은 없습니다. 대신에, 새로운 코드에서 기존 코드와 나란히 Hook을 사용할 계획입니다.

profile
웹 개발을 공부하고 있는 윤석주입니다.

0개의 댓글