어떻게 프로젝트를 시작하게 되었고, 진행하면서 느낀 개발자와 디자이너의 생생한 스토리를 직접 확인해보세요!
Development
Hook패턴
#Front-End
Yujin Son
2024. 10. 6.
Hook패턴
HOOK 패턴
Hooks패턴은 React 16.8버전에 추가된 기능입니다. Hooks가 디자인 패턴이 아닐순 있지만, 여러 전통적인 디자인 패턴들을 모두 Hooks로 변경할 수 있는 등 Hooks는 앱에서 아주 중요한 역할을 합니다.
1. 사용이유
React의 상태와 생명 주기 함수들을 ES2015의 클래스를 사용하지 않고 쓸 수 있게 해주기 때문입니다 .
2. 클래스 컴포너트에 대해 알아보자
Hooks가 추가되기 전, React에서 상태와 생명 주기 함수를 사용하려면 아래의 예시와 같이 클래스 컴포넌트를 꼭 사용해야했습니다.
클래스 컴포넌트는 생성자에서 상태를 선언한뒤, componentDidMount와 componentWillUnmout 같은 생명 주기 메서들들을 생명 주기를 기준으로 사이드 이펙트를 발생시켰습니다. 그 외에 추가 동작을 위한 메서드들을 선언할 수 있었습니다.
2.1 ES2015 클래스란
앞서 말했듯이, hooks가 추가되기 전에는 상태/생명 주기 메서드를 쓰려면 클래스 컴포넌트로 만들어야 했기 때문에 해당 기능을 쓰기 위해서는 종종 함수형 컴포넌트를 클래스형 컴포넌트로 리펙토링 해야만 했습니다.
‘div’로 만든 버튼을 예로 들어보겠습니다.
사용자가 버튼을 클릭했을 때에는 ‘enabled’로 보이도록 바뀌는 버튼을 만들기 위해 CSS를 추가하고 싶다면,
버튼이 enabled인지 disabled인지 상태를 유지해야 합니다.
따라서, 위와 같은 함수형 컴포넌트를 클래스형 컴포넌트로 리팩토링하고 상태를 가지도록 리팩토링해야 합니다.
위 예제는 리팩토링 과정이 간단하지만, 실무에서 사용되는 컴포넌트를 이렇게 리펙토링 하기는 매우 까다롭습니다. 리팩토링 과정에서 동작을 의도치 않게 변경하지 않으려면 ES2015클래스에 대해 알고 있어야 하기 때문입니다.
2.2 재설계
여러 컴포넌트에서 코드를 공유하기 위해 HOC패턴이나 Render Prop 패턴을 사용하지만, 나중에 이런 패턴들을 도입하려고 할때는 구조를 재설계해야 할 수도 있습니다.
또한, 컴포넌트의 크기가 클수록 앱을 재구성하기가 까다로우며, 컴포넌트를 많이 래핑하다보면 Wrapper Hell이라는 안티 패턴이 나타날 수 있습니다.
Wrapper Hell은 React나 다른 컴포넌트 기반 프레임워크에서 발생하는 안티패턴으로, 컴포넌트 구조가 너무 깊게 중첩되면서 코드의 가독성과 유지보수성이 떨어지는 상황을 말합니다. 특히 HOC, Context API를 많이 사용할 경우에 발생합니다.
이런 Wrapper Hell은 앱 내에서 데이터가 어떻게 흘러가는지 파악하기 어렵게 만들 뿐더러, 어떤 동작이 이뤄지고 있는지도 알기 어렵게 만듭니다.
2.3 복잡도
클래스 컴포넌트에 로직을 추가할수록 컴포넌트의 크기는 빠르게 증가합니다. 그럴수록 로직들은 서로 얽히고 분리하기 점점 어려워져서 디버깅과 성능 최척화에도 어려움을 갖게 합니다.
아래의 예제 코드에서도 생명주기 메서드들이 꽤 많은 코드의 중복을 만들어내는 것을 볼 수 있습니다.
복잡하게 얽힌 로직 뿐만 아니라 일부 로직은 생명주기 함수 내에서 중복되어있습니다. componentDidMount와 componentWillUnmount 양쪽에서 윈도우의 resize이벤트를 기준으로 동작을 커스터마이징 하고 있습니다.
3. Hooks
이렇게 개발자가 클래스 컴포넌트를 개발할 때 겪는 문제들을 해결하기 위해 React는 Hooks를 추가했습니다.
Hooks는 컴포넌트의 상태와 라이프사이클 메서드를 관리할 수 있는 함수입니다. React Hooks은 다음의 항목들을 가능하게 합니다.
💡
함수형 컴포넌트에 상태를 추가합니다.
💡
componentDidMount 혹은 componentWillUnmount 와 같은 생명주기 메서드 없이도 컴포넌트의 생명주기를 관리할 수 있습니다.
💡
앱 내에 상태를 가진 로직을 여러 컴포넌트에서 재사용할 수 있게 합니다.
3.1 Hooks를 사용해 함수형 컴포넌트에 상태를 추가하는 방법
1. Sate Hook
상태를 관리할 수 있도록 useState 훅을 제공합니다.
input요소 하나를 렌더링하고, 사용자의 타이핑을 상태에 업데이트하는 예제
클래스 컴포넌트를 사용했을때
useState 훅을 사용했을 때
2. Effect Hook
useState를 통해 함수형 컴포넌트 내에서 상태를 다룰 수 있었다면, useEffect 훅을 사용하면 컴포넌트의 생명주기를 다룰 수 있습니다.
예로, State hook section에서 다루었던 Input 예제를 활용해보겠습니다.사용자가 input요소에 포커스를 둔 채로 타이핑을 시작하면 이 값을 콘솔에 출력하려고 합니다.
3.1 Custom Hooks
React가 제공하는 빌트인 훅들
:useState, useEffect, useReducer, useRef, useContext, useMemo, useImperativeHandle, useLayoutEffect, useDebugValue, useCallback'을 이용하여 커스텀 훅을 직접 만들 수도 있습니다.
형광펜만 봐도 hook을 사용했을때 컴포넌트가 훨씬 명확하고 작은 조각으로 분리되어 가독성도 좋고, 재사용하기도 훨씬 수월한 것을 확인할 수 있습니다.
일반적인 훅에 대한 요약
useState
: 함수형 컴포넌트 내에서 상태를 관리하려 할 때 클래스형 컴포넌트로 변경하지 않아도 이를 가능케 하는 훅입니다. 다른 훅들에 비해 사용법이 단순합니다.
useEffect
: 코드를 컴포넌트의 생명 주기에실행하기 위한 훅입니다. 컴포넌트 함수 본문에서 변경을 하거나 뭔가를 구독하거나 타이머를 만들거나 로깅을 하는 등의 사이드 이펙트를 발생시키는것은 금지하고 있습니다. 허용될 경우 버그가 생기거나 모델과 뷰가 일치되지 않는 상황이 생길 수 있기 때문입니다. useEffect는 이런 사이드 이펙트가 발생하는것을 예방하고 UI가 부드럽게 동작하도록 한다. componentDidMount, componentDidUpdate, componentWillUnmount를 합친 것과 유사합니다.
useContext
: 컨텍스트 객체를 받아서 provider에 넘겼던 값을 반환합니다. React 의 Context API와 함께 동작하여 앱 전반적으로 공유하는 데이터를 prop drilling 없이 사용할 수 있게 해줍니다.
주의해야 할 점은 useContext의 인자로는 컨텍스트 객체를 넘겨야 하고, useContext를 사용하는 컴포넌트는 컨텍스트 업데이트 시 마다 리렌더링 된다는 점입니다.
useReducer
: setState대신 사용할 수 있으며 특히 이전 상태에서 다음 상태로 변경될 때 복잡한 상태 로직과 파생되는 변수들을 만들어 내야 하는 경우 더 선호됩니다. reducer 함수와 초기 값을 인자로 받아 초기값과 dispatch함수를 반환합니다. useReducer 는 또한 컴포넌트의 복잡한 상태에서 깊이 선언된 값을 변경할 때의 성능에 최적화되어 있습니다.
Hook의 장점과 단점
장점
코드가 간결해집니다. 생명주기와 얽히지 않으며 코드들은 관심사와 기능에 따라 분류할 수 있기 때문입니다.
복잡한 컴포넌트를 단순화 해줍니다.
: 자바스크립트의 클래스는 관리하기 힘들고 hot reloading과 함께 쓰기 어려우며 minifiy도 잘 되지 않습니다. 또한, 상태 로직이 있는 자바스크립트의 클래스를 사용하는 것은 여러 단계의 상속 구현을 유도하고 전체적인 복잡도를 빠르게 증가시키며 에러를 발생하기 쉽게 만들지만, 훅은 상태를 만드는 것 뿐만 아니라 React의 기능들을 클래스 없이 작성할 수 있습니다. Hook을 사용하면 상태를 가진 로직을 계속 재작성하지 않고 재사용할 수 있습니다. 에러를 발생시킬 확률을 낮추며 일반 함수를 조립해서 쓸 수 있게 해 줍니다.
훅이 구현되기 전에는 React에서 뷰가 없는 로직을 추출해내기가 어려웠지만, 이는 HOC패턴이나 render prop패턴을 사용할 때 복잡도를 증가시키는 원인이 되었습니다. 그렇지만 훅이 추가되면서 상태가 있는 로직을 언제든 자바스크립트 함수로 분리할 수 있게 되었습니다.
단점
규칙에 따라 작성해야 합니다. 정적 분석기 플러그인을 사용하지 않으면 어떤 훅이 규칙을 어기고 있는지 알기 어렵습니다.