일.출발점
React 의 기존 컴포넌트 모델 하에서는, 많은 해결하기 어려운 문제들이 존재합니다:
-
컴포넌트 간 상태 로직을 재사용하기 어려움
-
컴포넌트 복잡도가 높아 이해하기 어려움
-
Class 의 많은 폐해
-
……
그리고 Hooks 는, 국면을 타개하는 사명을 짊어지고 있습니다
컴포넌트 간 로직 재사용
컴포넌트 간 로직 재사용은 항상 문제였습니다. Render Props, Higher-Order Components 등의 일반적인套路패턴은 모두 횡단적 관심사(Cross-cutting concern)를 분리하고, 다음과 같은 것들을 재사용하기 위한 것입니다:
-
로그
-
캐시/동기화/지속화
-
데이터 검증
-
에러 캡처/예외 처리
의 로직으로, 목적은 횡단적 관심사와 코어 비즈니스 로직을 분리하여, 비즈니스 로직에 집중할 수 있도록 하는 것입니다
P.S. 애스펙트, 관심사 등의 AOP 개념에 대한 상세 정보는, AOP(Aspect-Oriented Programming) 참조
그러나, HOC 와 Render Props 는 컴포넌트 형식으로 횡단적 관심사를 분리할 수 있지만, 몇 가지 새로운 문제도 가져옵니다:
-
확장성 제한
-
Ref 전달 문제
-
Wrapper Hell
이러한 문제들이 발생한 근본적인 이유는:
세밀한 코드 재사용은, 컴포넌트 재사용과 捆绑되어서는 안 됩니다
그리고一直以来 단순하고 직접적인 컴포넌트 행동 확장 방식이 부족했습니다:
React doesn't offer a way to "attach" reusable behavior to a component (for example, connecting it to a store).
Hooks 를 제안한 주요 목적은 이 문제를 해결하기 위해서입니다:
React needs a better primitive for sharing stateful logic.
P.S. 컴포넌트 간 로직 재사용 방식에 대한 상세 정보는, [React 컴포넌트 간 로직 재사용](/articles/react 컴포넌트 간 로직 재사용/) 참조
컴포넌트 복잡도 문제
아시는 바와 같이, React 컴포넌트는 점점 더 복잡해지고 있습니다:
Provider, Consumer, Higher-order Component, Render Props
// with Redux
Action, Reducer, Container
// with Reselect
Selector
// with xxx ...
등의 많은 추상층은 상태 로직의 조직과 재사용 문제를 완화했지만, 그에 따라 컴포넌트 재사용 비용이 더 높아졌고, 더 이상 단순히 컴포넌트를 도입하는 것만으로 완전한 비즈니스 기능을 얻을 수 없게 되었습니다
확실히, 이러한 세분화된 추상층은 코드 책임을 더욱 명확하게 할 수 있지만, 상태 로직도 분산되어 재사용하기 어려워집니다. 따라서 컴포넌트 재사용도는 대부분 View Component (View + UI State Logic) 레벨에 머물러 있으며, Business Component (View + Business State Logic) 를 더욱 재사용할 수는 없습니다. 이러한 비즈니스 상태 로직(데이터 요청, 다른 데이터 소스 구독, 타이머 등)을 모두 단일 컴포넌트에 집약하지 않는 한(예를 들어 mobxjs/mobx 로 변경하여 상태 관리).即便如此, 컴포넌트에 혼재하는 부작용과, 라이프사이클 메서드에 혼재하는 무관한 로직을 피할 수는 없습니다. 예를 들어 componentDidMount 에는 데이터 요청, 이벤트 리스닝 등이 ��함되어 있습니다……我们发现,진정으로 관련성이 있는 코드는 라이프사이클에 의해 분리되고, 완전히 무관한 코드가 최종적으로 하나의 메서드에 모여 있습니다:
Mutually related code that changes together gets split apart, but completely unrelated code ends up combined in a single method.
컴포넌트 라이프사이클에 따라 로직을 분할하면 컴포넌트体积가 급속히 팽창하고, 이러한 거대 컴포넌트를 일련의 작은 컴포넌트로 분할하기는 쉽지 않습니다. 상태 로직이 도처에 있고, 테스트하기도 어렵기 때문입니다
따라서, 보다 합리적이고, 보다 재사용하기 쉬운 상태 로직 조직 방식 이 필요합니다. 진정으로 관련성이 있는 코드를 함께 모으고, 라이프사이클 메서드에 의해 강제로 분리되지 않도록 합니다
P.S. MobX 에 대한 상세 정보는, MobX 참조
Class 의 폐해
Class 는 객체의 형으로서, OOP 의 매우 중요한 부분입니다. 그러나, Class 를 사용하여(뷰와 로직이 결합된)컴포넌트를 정의하는 것은 그다지 이상적이지 않습니다:
-
코드 조직/재사용이 어려워짐
-
더 높은 학습 비용을 가져옴
-
컴파일 최적화를 저해함
코드 조직/재사용 측면에서는, Class 의 라이프사이클 메서드가 전형적인 예입니다. 하나의 컴포넌트에는 특정 라이프사이클 메서드가 하나만 존재할 수 있으므로, 몇 가지 무관한 로직을 함께 배치해야 하고, 이러한 [템플릿식의 분할](/articles/디자인 패턴 之 템플릿 메서드 패턴(template-method-pattern)/) 은 진정으로 관련성이 있는 코드를 분리하여, 코드 재사용에 불리합니다
학습 비용은, 주로 두 점에 나타납니다:
-
JavaScript 중의
this를 이해할 필요가 있음(다른 언어와 다름), 그리고bind(this)를 기억해 둬야 함 -
함수형 컴포넌트와 Class 컴포넌트의 차이, 및 각각의 적용 장면을 이해할 것
컴파일 최적화 측면에서는, Class 로 인해 몇 가지 도구의 최적화 효과가 크게 떨어집니다. 예를 들어:
-
컴포넌트 사전 컴파일(ahead-of-time compilation)효과가 이상적이지 않음(React 팀은 이미 이 방면에서 몇 가지 시도 를 수행했고, Class 컴포넌트가 컴파일 최적화에 불리하다는 것을 발견)
-
Class 는 코드 압축에 불리
-
올바른 핫 리로드(hot reloading)가 어려움
P.S. 컴포넌트 사전 컴파일은 [GCC 의高级模式](/articles/react 배후의 도구화 체계/#articleHeader11) 와 유사하며, defaultProps 상수 등을 인라인 최적화하고, 불필요한 코드를 제거
따라서, 컴파일 최적화에 친숙한 API 를 제공하고 싶음:
We want to present an API that makes it more likely for code to stay on the optimizable path.
그래서 Class 를 버리고, 함수를 받아들임:
Hooks let you use more of React's features without classes.
P.S. 함수형 프로그래밍으로 전환하고, FP 개념을 전면적으로 도입하는 것이 아니라, 명령형 프로그래밍으로의 "탈출 해치"를 제공하는 것으로, 함수형 프로그래밍, 반응형 프로그래밍 등의 기술을 습득할 필요는 없음:
Hooks provide access to imperative escape hatches and don't require you to learn complex functional or reactive programming techniques.
이.목표
이상의种种의 문제를 해결하기 위해, Hooks 가 탄생했습니다. 목표는:
-
단순하고 직접적인 코드 재사용 방식 제공
-
보다 합리적인 코드 조직 방식 제공
-
Class 의 대체 방안 제공
한편으로 코드 조직, 재사용 문제를 해결하고, 다른 한편으로, 새로운 컴포넌트 정의 방식도 React 의 미래 비전의 일부입니다:
Hooks represent our vision for the future of React.
그렇다면, Hooks 는 도대체 무엇일까요?
삼.위치付け
Hooks 는 함수형 컴포넌트가 React 의 State 와 라이프사이클 등의 기능에 액세스할 수 있게 하는 함수 입니다:
Hooks are functions that let you "hook into" React state and lifecycle features from function components.
한편으로 Hooks 를 통해 코드를 보다 합리적으로 분할/조직하고, 재사용 문제를 해결하며, 다른 한편으로 Hooks 를 통해 함수형 컴포넌트를 강화하여, Class 컴포넌트와 동일한 표현력을 갖게 하고, 대체 옵션이 되며, 최종적으로 대체합니다
사.작용
Hooks 는 주로 코드 조직, 로직 재사용 방면의 문제를 해결합니다. 예를 들어:
-
라이프사이클에 의해 분리된 관련 로직을 조직. 데이터 소스 구독/구독 해제, 이벤트 리스너 등록/해제 등
-
라이프사이클 중에 산재한 중복 로직을 컴포넌트 간에 재사용하고, 동시에 HOC 와 Render Props 등의 컴포넌트 합성에 기반한 재사용 모드가 가져오는 컴포넌트 네스트 문제(Wrapper Hell)를 해결
게다가, React 자신에게 있어, Hooks 는 대규모 최적화의 장애물도 해결합니다. 예를 들어 컴포넌트 인라인 컴파일의 난제 등
코드 조직
Hooks 방안 하에서, 가장 큰 차이점은, 컴포넌트를 라이프사이클 메서드에 따라 강제로 분할하는 것이 아니라, 코드 블록의 내재적 관련성에 기반하여 몇 개의 작은 함수로 분할할 수 있다 는 것입니다:
Hooks let you split one component into smaller functions based on what pieces are related (such as setting up a subscription or fetching data), rather than forcing a split based on lifecycle methods.
예를 들어:
// 커스텀 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;
}
외부 데이터 소스의 등록/해제 코드가 밀접하게联系在一起, 컴포넌트 라이프사이클)의 차이를 신경 쓸 필요가 없습니다
로직 재사용
동시에, 위의 예시와 같이, 이러한 상태 로직과 부작용을 쉽게 Hooks 로 추출하고, Custom Hook 으로 구성할 수 있어, 상태 로직의 재사용 문제를 깔끔하게 해결합니다:
With Hooks, you can extract stateful logic from a component so it can be tested independently and reused.
예를 들어:
// View 컴포넌트 1
function FriendStatus(props) {
// 커스텀 Hook 사용
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
// View 컴포넌트 2
function FriendListItem(props) {
// 커스텀 Hook 사용
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
다른 한편으로, 이러한 재사용 방식은 "무상"으로, 컴포넌트 트리의 계층 구조를 조정할 필요 없이, 도입하여 바로 사용 할 수 있습니다:
Hooks allow you to reuse stateful logic without changing your component hierarchy. This makes it easy to share Hooks among many components or with the community.
오.정리
단순히 형식적으로 보면, Hooks 는 함수형 컴포넌트의 강화로, Class 컴포넌트와 대등할 수 있고, 더욱이(기대적으로는)대체할 수 있습니다. 실질적인 의미는 더욱 많은 함수형 사상을 프런트엔드 영역에 도입하는 것 입니다. 예를 들어 Effect, Monad 등. v = f(d) 의 UI 층 함수형 사상을 제안한 후, 이 길에서의 추가적인 탐구라고 할 수 있습니다
(React 16 Roadmap 에서 인용)
어떤 의미에서는, 이러한 사상 폭풍은 Concurrent Mode 등의 핵심 특성보다 더 흥분되는 것입니다
아직 댓글이 없습니다