HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
👻
개발 기록
/
📚
CS 스터디
/
📚
react hook 등장 배경
📚

react hook 등장 배경

개요

  • Hook을 이용하여 기존 Class 바탕의 코드를 작성할 필요 없이 상태 값과 여러 React의 기능을 사용할 수 있다.
 

동기

  • 컴포넌트 사이에서 상태 로직을 재사용하기 어렵다.
    • React는 컴포넌트 간에 재사용 가능한 로직을 붙이는 방법을 제공하지 않는다.
    • 이를 강제하기 위해 render props나 고차 컴포넌트같은 패턴을 통해 이를 해결했다.
      • //render props <DataProvider render={data => ( <h1>Hello {data.target}</h1> )}/> // 고차 컴포넌트(HOC, Higher Order Component) // 컴포넌트를 가져와 새 컴포넌트를 반환하는 함수입니다. const EnhancedComponent = higherOrderComponent(WrappedComponent);
    • 하지만 이런 방법은 컴포넌트의 재구성을 강요하며, 코드의 추적을 어렵게 만든다.
    • providers, consumers, 고차 컴포넌트, render props 그리고 다른 추상화에 대한 레이어로 둘러싸인 “래퍼 지옥(wrapper hell)“을 볼 가능성이 높다.
      providers, consumers, 고차 컴포넌트, render props 그리고 다른 추상화에 대한 레이어로 둘러싸인 “래퍼 지옥(wrapper hell)“을 볼 가능성이 높다.
    • React는 상태 관련 로직을 공유하기 위해 좀 더 좋은 기초 요소가 필요했다.
  • 복잡한 컴포넌트의 이해가 어렵다.
    • 각 생명주기 메서드에는 자주 관련 없는 로직이 섞여들어가고는 한다.
    • 예시로 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 }); } // ...
      class
    • 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); }; }); // ... }
      hooks
  • 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의 상태에 결합할 수 있다.
 
 
 
참고 자료 :
Hook의 개요
[React] Hook의 동작 원리 이해하기
[번역] 심층 분석: React Hook은 실제로 어떻게 동작할까?
React Hook의 어두운면