개요
- Hook을 이용하여 기존 Class 바탕의 코드를 작성할 필요 없이 상태 값과 여러 React의 기능을 사용할 수 있다.
동기
- 컴포넌트 사이에서 상태 로직을 재사용하기 어렵다.
- React는 컴포넌트 간에 재사용 가능한 로직을 붙이는 방법을 제공하지 않는다.
- 이를 강제하기 위해
render props
나고차 컴포넌트
같은 패턴을 통해 이를 해결했다.
//render props <DataProvider render={data => ( <h1>Hello {data.target}</h1> )}/> // 고차 컴포넌트(HOC, Higher Order Component) // 컴포넌트를 가져와 새 컴포넌트를 반환하는 함수입니다. const EnhancedComponent = higherOrderComponent(WrappedComponent);

- 복잡한 컴포넌트의 이해가 어렵다.
- 각 생명주기 메서드에는 자주 관련 없는 로직이 섞여들어가고는 한다.
- 예시로
componentDidMount
와componentDidUpdate
는 컴포넌트안에서 데이터를 가져오는 작업을 수행할 때 사용 되어야 하지만, 같은componentDidMount
에서 이벤트 리스너를 설정하는 것과 같은 관계없는 로직이 포함되기도 하며,componentWillUnmount
에서 cleanup 로직을 수행하기도 한다. - 상태 관련 로직은 한 공간안에 묶여 있기 때문에 이런 컴포넌트들을 작게 분리하는 것은 불가능하며 테스트하기도 어렵다.
class FriendStatusWithCounter extends React.Component { constructor(props) { super(props); this.state = { count: 0, isOnline: null }; this.handleStatusChange = this.handleStatusChange.bind(this); } componentDidMount() { document.title = `You clicked ${this.state.count} times`; ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentDidUpdate() { document.title = `You clicked ${this.state.count} times`; } componentWillUnmount() { ChatAPI.unsubscribeFromFriendStatus( this.props.friend.id, this.handleStatusChange ); } handleStatusChange(status) { this.setState({ isOnline: status.isOnline }); } // ...
document.title
을 설정하는 로직이 componentDidMount
와 componentDidUpdate
에 나누어져 있습니다. 구독(subscription)로직 또한 componentDidMount
와 componentWillUnmount
에 나누어져 있네요. componentDidMount
가 두 가지의 작업을 위한 코드를 모두 가지고 있습니다.function FriendStatusWithCounter(props) { const [count, setCount] = useState(0); useEffect(() => { document.title = `You clicked ${count} times`; }); const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); // ... }
- Class는 사람과 기계를 혼동시킨다.
- React 에서의 Class 사용을 위해서는 JavaScript의
this
키워드가 어떻게 작동하는지 알아야만 한다. JavaScript의this
키워드는 대부분의 다른 언어에서와는 다르게 작동함으로 사용자에게 큰 혼란을 주었으며, 코드의 재사용성과 구성을 매우 어렵게 만들고는 했다. - Hook은 Class없이 React 기능들을 사용하는 방법을 제시한다.
동작 원리
- 기본적으로 Hooks는 UI의 상태 관련 동작 및 부수 작용(side effects)을 캡슐화하는 가장 간단한 방법이다.
- useState 구현하기
- . state를 getter함수로 만들고, count함수 값을 호출해서 값을 얻을 수 있다.
const React = (function () { let hooks = []; let idx = 0; //여러 hook이 사용될 것을 고려함. function useState(initialVal) { const state = hooks[idx] || initialVal; const _idx = idx; const setState = (newVal) => { hooks[_idx] = newVal; }; idx++; return [state, setState]; } return { useState }; })(); function Component() { const [count, setCount] = React.useState(1); const [text, setText] = React.useState("apple"); return { render: () => console.log({ count, text }), click: () => setCount(count + 1), type: (word) => setText(word), }; } var App = React.render(Component); // { count: 1, text: 'apple' } App.click(); var App = React.render(Component); // { count: 2, text: 'apple' } 😀 App.click(); var App = React.render(Component); // { count: 3, text: 'apple' } 😀 App.type("orange"); var App = React.render(Component); // { count: 3, text: 'orange' } 😀 App.type("peach"); var App = React.render(Component); // { count: 3, text: 'peach' } 😀
- useEffect 구현하기
const React = (function () { let hooks = []; let idx = 0; function useState(initialValue) { //... } function useEffect(cb, depArray) { const oldDeps = hooks[idx]; let hasChanged = true; // default if (oldDeps) { hasChanged = depArray.some((dep, i) => !Object.is(dep, oldDeps[i])); //첫번째 인자와 두번째 인자가 같은지를 판정하는 메서드 } // 변경을 감지 if (hasChanged) { cb(); } hooks[idx] = depArray; idx++; } return { useState, useEffect }; })();
- 구현 결론
- 클로저가 사용된다.
- 배열과 인덱스로 만들었다.
- 마술이 아니다.
단점
개인의 의견으로만 받아들이기.
- this를 사용하지 않는 게 맞을까?
- 컴포넌트의 상태와 연관된 로직을 재사용하기 어렵다.
- useEffect로 코드가 간결해지는 건 사실이지만 이것 자체로 함수형 컴포넌트를 사용하는 이유가 되선 안된다.
- 정말 성능상 더 좋을까?
useMemo
,useCallback
등을 사용하여 Funclass를 최적화하려고 하면, 클래스보다 더 장황한 코드로 끝날 수도 있다.
- 부족한 선언성.
- 클래스를 사용하면
render
함수만 검색하면 되지만, Funclass를 사용하면 메인return
문을 찾기가 어려울 수 있다. 또한 코드를 어디서 찾을지 힌트를 제공하는 일반적인 라이프 사이클 메서드와 달리, 다른useEffect
문을 따라서 컴포넌트의 흐름을 이해하는 것이 더 어렵다.
- 모두 react에 묶임.
- 커스텀 Hook은 순수한 로직을 React의 상태에 결합할 수 있다.
참고 자료 :