Lambda
article thumbnail

최근 진행한 Frontend 스터디 시간엔 React Hooks에 대한 학습을 진행하였습니다. 

React Hooks는 React 16.8 버전(2019년도)에 추가된 공식 라이브러리입니다. 제가 항상 강조하는 부분이지만 어떤 기술 스택을 배우기 이전에 '도입하게된 이유'를 학습하는 것은 해당 기술에 대한 다채로운 이해를 돕습니다. 따라서 이번 React Hooks 포스팅은 Hook의 도입 이전에는 어떠한 방식을 사용했고, React Hooks는 왜 도입하게 되었으며, React Hooks의 종류와 사용법에 대해 정리하였습니다. 

이번 포스팅은 '이야기'의 형식을 사용하여 평소보다는 긴 호흡으로 작성하였습니다. 

 

 

Class Component와 Function Component

과거 React는 Class Component를 기반으로 작업을 진행했습니다.

class App extends Component {
  render() {
    return <div>안녕!</div>
  }
}

Class Component를 이용해 만들어낸 컴포넌트는 말그대로 하나의 객체처럼 동작합니다. Class Component에선  this를 통해 자기 자신을 칭하고, 무언가 변화가 생기면 render()메서드를 다시 호출해 리렌더링을 진행하는 방식을 사용합니다. 이런 구조에서 메서드의 결과물은 상태의 영향을 받게 됩니다.

이는 분명히 장점도 있겠지만 이로 인해 생기는 불편한 점도 분명히 있습니다.

예를 들어 특정 메모리를 차지하고 있는 객체의 상태가 변경되어서 리랜더링되어야 하는 상황을 가정해보겠습니다. name이라는 state 값이 원래 ‘iOS’였다가, ‘Web’으로 변경되었습니다. 하지만 상태가 변경되기 직전에 어떤 비동기 작업이 시작되었습니다. 이 비동기 작업은 작업이 끝나고 나면 name이라는 state 값을 출력합니다.

위 상황의 경우 비동기 작업이 끝났을 때 콘솔에는 ‘Web’이 출력됩니다. 이는 개발자가 의도한 결과가 아닙니다.

이처럼 Class Component의 경우 상태에 따라 그 결과 값이 의도치 않게 변한다는 단점을 가집니다.

 

React에선 Function Component방식으로도 작업을 진행할 수 있습니다. Function Component는 React 컴포넌트가 함수의 결과값으로 반환되는 방식을 사용합니다.

아래의 코드는 Function Component를 기반으로 작성된 리액트 코드입니다.

const App = ({ name }) => {
  return (<div>{`안녕! ${name}`}</div>)
};

Functional Component는 위에서 말한 Class Component의 단점을 해결해주는 불변성이라는 특징이 있습니다.

위의 코드에서 매개변수인 name은 변하지 않습니다. 일단 이 함수형 컴포넌트가 리렌더링, 즉 다시 호출되기 전까지는 name처음 값을 유지합니다. 이처럼 Functional Componen는 의도치 않은 변화를 방지할 수 있고, 그 내용을 바꾸기 위해서는 새로운 매개변수 값을 넣어서 다시 호출(리렌더링)해야 합니다.

따라서 this가 가지고 있는 값이 어떻게 변할지 모르는 Class Component와 달리, Functional Component에선 어떤 값이 Immutable하다는 확신을 가지고 구현할 수 있습니다. 결과적으로 개발자가 의도한 대로 코드가 동작할 가능성이 높아지고, 그에 따라 Side Effect가 줄어들 것입니다. 즉, Functional Component는 함수형 프로그래밍의 장점인 순수성, 불변성을 바탕으로 Class Component가 가지고 있는 여러 문제를 해결해줍니다.

 

위에서 언급한 Function Component의 장점으로 인해 많은 개발자들이 Function Component를 활용한 개발을 시도했습니다. 하지만 Function Component는 기존의 Class Component를 대체하기엔 다소 무리가 있었습니다. 이는 Class Component가 제공하는 강력한 기능들이 있었기 때문입니다.

Class Component가 제공하는 기능을 정리하면 다음과 같습니다.

1. Class Component는 statesetState 를 이용해 상태를 관리할 수 있습니다.

2. 컴포넌트의 Life Cycle에 맞춰 개발자가 원하는 대로 명령을 수행할 수 있습니다.

컴포넌트의 생명 주기(Life Cycle)

Functional Component는 Class Components와 달리 렌더링할 때마다 함수가 새롭게 불리기 때문에 상태 관리가 쉽지 않을 뿐 아니라 Life Cycle은 어떻게 처리해야 할지 방법조차 묘연한 상황이었습니다.

따라서 간단한 컴포넌트는 함수형으로 만들 수 있었지만, 앞서 언급한 Class Component가 제공하는 기능 덕분에 React에선 Class Component를 주력으로 사용할 수 밖에 없었습니다.

하지만 React 16.8부터 상황이 달라졌습니다. 바로 Function Component에서도 상태 관리, Life Cycle 관리를 할 수 있도록 도와주는 Hook이 등장한 것입니다.

렌더링(Rendering) 이란?
React 내에서는 컴포넌트 렌더링이라고도 하며, 컴포넌트 내에 엘리먼트 요소들(HTML, React 사용자 정의 태그)을 화면상에 그리는 동작을 의미합니다.
리 렌더링(re-Rendering) 이란?
컴포넌트 내에 엘리먼트 요소들을 코드를 기반으로 화면의 그리는 작업을 다시 수행하는 것을 의미합니다. 

 

React Hooks란

React Hooks는 React 16.8 버전(2019년도)에 추가된 공식 라이브러리입니다.

React Hooks를 사용하면 클래스형 컴포넌트에서 사용하던 상태 관리, Life Cycle 관리를 함수형 컴포넌트에서도 사용할 수 있습니다. React Hooks를 이용하면 함수형 컴포넌트에서도 클래스형 컴포넌트 부럽지 않은 기능을 구현할 수 있었기 때문에 클래스형 컴포넌트의 단점을 여실히 느끼고 있던 개발자들에게 큰 반향을 일으켰습니다.

현재 공식문서에서는 클래스형 컴포넌트보다는 함수형 컴포넌트로 새로운 React 프로젝트를 만들기를 권장하고 있습니다.

 

React Hooks는 과연 왜 필요한가

함수형 컴포넌트들은 기본적으로 리렌더링이 될 때 함수 안에 작성된 모든 코드가 다시 실행됩니다. 이러한 이유로 함수형 컴포넌트는 기존에 가지고 있던 상태(state)를 관리(기억)할 수 없게 됩니다.

하지만 Hook의 등장으로, 브라우저에 메모리를 할당함으로써 함수형 컴포넌트에서도 상태와 생명주기를 다룰 수 있게 되었습니다.

하지만 Hook은 브라우저의 메모리 자원을 사용하기에 지나치게 남발하면 오히려 성능 저하를 불러올 수있습니다.

 

Hook을 사용하면 구체적으로 어떤점이 좋은가?

Hook을 사용하였을 때 얻을 수 있는 장점을 정리하면 다음과 같습니다.

1. Component 사이에서 상태 로직을 추상화 할 수 있다.

기존에는 render props나 고차 컴포넌트(HOC, Higher Order Component)를 사용하여  Component간 재사용 가능한 상태 로직을 구현하였지만 이는 상당히 난이도가 있는 작업이었습니다.

Hook을 사용하면 Component로부터 상태 관련 로직을 추상화하여 쉽게 상태 공유가 가능합니다.

2. 복잡한 컴포넌트를 작은 함수의 묶음 component로 나눌 수 있다.

기존에는 각 생명주기 메서드에 서로 연관 없는 로직이 섞여 있어 버그가 쉽게 발생하고 무결성을 해치는 코드를 작성하였습니다. 이러한 이유로 개발자들은 React를 별도의 상태 관리 라이브러리와 함께 사용되어 왔습니다.

Hook을 사용하면 작은 함수의 묶음으로 Component를 나눌 수 있게 되어 이러한 문제를 해결할 수 있습니다.

3. Class 없이 React 기능들을 사용할 수 있다.

기존의 React에서 Class를 사용하기 위해선 this키워드가 동작 방식에 대해 이해가 선행되어야 했습니다. 또한 React 내의 함수와 Class Component의 구별, 각 요소의 사용 타이밍 등은 React 개발자 사이에서도 의견이 갈리는 문제였습니다. Class는 코드의 최소화를 힘들게 만들고, Hot reloading이 정상 동작 하지 않게 만드는 문제 또한 포함하였습니다.

Hook은 이러한 문제를 해결하기 위해서 Class 없이 React의 기능들을 사용하는 방법들을 제공하고 있습니다.

 

 

Hook 규칙

Hook에는 규칙이 있습니다. 이를 반드시 준수해야 정상적으로 Hook이 실행됩니다.

보통 CRA를 통해 eslint-plugin-react-hooks(ESLint 플러그인)을 사용하여 아래 두 규칙을 강제합니다.

1. 최상위에서만 Hook을 호출해야한다.

반복문이나 조건문 혹은 중첩된 함수 내에서 Hook을 호출하면 안됩니다. 리액트 훅은 호출되는 순서에 의존하기 때문에 조건문이나 반복문 안에서 실행하게 될 경우 해당 부분을 건너뛰는 일이 발생할 수 있습니다. 그렇기 때문에 이 규칙을 따르면 useState와 useEffect가 여러번 호출되는 경우에도 Hook의 상태를 올바르게 유지할 수 있습니다.

2. 리액트 함수 내에서만 Hook을 호출해야한다.

Hook은 일반적인 js함수에서는 호출하면 안됩니다.

 

 

자주 사용하는 React hook 알아보기

1. useState

useState는 Hook에서 가장 기본이 되는 함수로, 인자로 초기 state 값을 하나 받고, 현재의 state 값과 이 값을 업데이트 하는 함수를 같이 제공합니다. 이는 this.setState와 유사하지만, 이전 state와 새로운 state 를 합치지 않는다는 차이가 있습니다.

[state이름, setter이름]순으로 반환 받아서 사용합니다.

useState가 가지는 특징을 정리하면 다음과 같습니다.

  • 현재 상태를 나타내는 state값과 상태를 변경하는 setState값을한 쌍으로 제공합니다.
  • state는 초기값을 설정할 수 있으며, 초기값은 첫 렌더링 때 한번만 사용됩니다.
  • state는 객체일 필요 없이 문자열, 숫자, boolean, 배열, null, 객체 등 다양한 값을 넣을 수 있습니다.
import React, { useState } from 'react';

function Example() {
  // "count"라는 새 상태 변수를 선언합니다
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

 

2. useEffect

useEffect는 함수 Component 내에서 데이터를 가져오거나 구독하고, DOM을 직접 조작하는 작업 등을 수행 할 수 있게 해줍니다.

Class Component 의 componentDidMount, componentDidUpdate, componentWillUnmount와 같은 목적으로 제공되지만, 하나의 api로 수행할 수 있게 해줍니다.

useEffect가 가지는 특징을 정리하면 다음과 같습니다.

  • useEffect는 기본적으로 useEffect(function, deps)의 형태를 가집니다.
  • function는 useEffect가 수행될 때 실행되는 함수를 의미합니다.
  • deps는 배열 형태이며, 의존(dependency)값을 의미합니다.
  • 즉 function에는 실행시킬 함수를 넣고, deps에는 의존성 배열을 담습니다.
  • 의존성 배열에 어떤 것이 담기냐에 따라 부수적인 효과 함수가 실행됩니다.
effect 타이밍
useEffect로 전달된 함수는 컴포넌트 렌더링 - 화면 업데이트 - useEffect실행 순으로 실행이 됩니다.
즉, useEffect실행이 최초 렌더링 이후에 된다는 것을 유의해야 합니다. 만약 화면을 다 그리기 전에 동기화 되어야 하는 경우에는useLayoutEffect를 활용하여 컴포넌트 렌더링 - useLayoutEffect 실행 - 화면 업데이트 순으로 effect를 실행시킬 수 있습니다.
import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  // componentDidMount, componentDidUpdate와 비슷합니다
  useEffect(() => {
    // 브라우저 API를 이용해 문서의 타이틀을 업데이트합니다
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

useEffect의 사용법에 대해 구체적으로 알아보겠습니다.

  • 의존성 배열 없이 useEffect를 실행시킬 경우, 페이지가 렌더링 될 때마다 함수를 실행합니다.
  • 의존성 배열에 빈배열을 담아주게 될 경우, 첫 렌더링 시에만 함수를 실행합니다.
  • 의존성 배열에 state값이나 props로 상속받은 데이터값 등을 담아주게 될 경우, 해당 값이 변할 때마다 함수를 실행합니다.

이를 도식화하면 아래 그림과 같습니다.

useEffect와 의존성 배열

 

모든 effect는 clean-up을 위한 함수를 반환할 수 있습니다. 이 때 useEffect안에서의 return은 정리 함수(clean-up)를 사용하기위해 쓰여집니다.

clean-up함수와 관련된 내용을 정리하면 다음과 같습니다.

  1. 메모리 누수 방지를 위해 UI에서 컴포넌트를 제거하기 전에 수행됩니다.
  2. 컴포넌트가 여러 번 렌더링 된다면, 다음 effect가 수행되기 전에 이전 effect가 정리됩니다.

만약 useEffect 내에서 Subscribe 작업이나 비동기 작업 등 메모리 누수가 될 수 있는 작업을 실행할 경우에는 반드시 clean-up함수를  이용해 해당 작업들을 취소해야합니다.

effect를 실행하고 이를 정리(clean-up)하는 과정을 “마운트와 마운트 해제 시에” 딱 한 번씩만 실행하고 싶다면, 빈 배열([])을 두 번째 인수로 넘기면 됩니다. 빈 배열([])을 넘기게 되면 effect 안의 prop과 state는 초기값을 유지합니다.

 

3. useRef

useRef는 특정 DOM에 접근하여 DOM 조작을 가능하게 하는 훅입니다. 특정 DOM을 선택할때 주로 쓰이며 current 프로퍼티에 전달된 인자로 초기화된 ‘변경 가능한 ref 객체’를 반환합니다. 반환된 객체는 컴포넌트의 전체 생애주기 동안 유지됩니다.

import { useRef } from 'react';

export default function Counter() {
  let ref = useRef(0);

  function handleClick() {
    ref.current += 1;
    alert('You clicked ' + ref.current + ' times!');
  }

  return (
    <button onClick={handleClick}>
      Click me!
    </button>
  );
}

 

4. useMemo

메모이제이션된 값을 반환합니다. 이미 연산 된 값을 리렌더링 시 다시 계산하지 않도록 합니다.

의존성이 변경되었을 때에만 메모이제이션된 값만 다시 계산하며, 의존성 배열이 없는 경우 매 렌더링 때마다 새 값을 계산합니다.

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

 

5. useCallback

메모이제이션 된 콜백을 반환합니다. 때문에 특정 함수를 새로 만들지 않고 재사용가능하게 합니다.

useMemo와 유사하게 이용되며, '함수'에 적용하여 사용합니다. 의존성이 변경되었을 때만 메모이제이션된 함수를 변경합니다.

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);
useMemo vs useCallback
useMemo와 useCallback은 모두 메모이제이션과 관련이 있으며 성능 개선을 위해 많이 사용됩니다.
여기서 메모이제이션은 기존에 수행한 연산의 결과값을 저장해두었다가 동일한 입력이 들어오면 저장한 값을 재활용하는 프로그래밍 기법입니다. 그렇기 때문에 useMemo나 useCallback을 적절하게 사용하면 중복 연산을 피할 수 있어 어플리케이션의 성능을 최적화할 수 있습니다.

useMemo와 useCallback은 비슷하게 동작하지만 차이가 존재합니다. useMemo의 경우에는 메모이제이션된 값을 반환하지만, useCallback은 메모이제이션된 함수를 반환합니다.

 

6. useContext

Context API를 통해 만들어진 Context에서 제공하는 value를 사용할 수 있게 합니다

const value = useContext(MyContext);

위의 예시의 경우 <MyContext.Provider>가 갱신되면, 이 Hook은 그 MyContext provider에게 전달된 가장 최신의 context value를 사용하여 렌더러를 트리거 합니다.

 

7. useReducer

useState의 대체 함수로 컴포넌트 상태 업데이트 로직을 컴포넌트에서 분리시킬 수 있습니다. 컴포넌트 바깥에 로직을 작성할 수 있고, 심지어 다른 파일에 작성한 후 불러와 사용할 수도 있습니다.

reducer현재 상태액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 함수입니다.

const [state, dispatch] = useReducer(reducer, initialArg, init);

 

 

마치며

지금까지 React Hooks는 왜 도입하게 되었으며, React Hooks의 종류와 사용법에 대해 알아보았습니다.

 

이전 포스팅들도 나름의 방식으로 포스팅의 주제를 풀어나갔지만, 이번 포스팅의 경우엔 'React Hooks를 도입하게 된 이유'와 'React Hooks 가이드'를 연결하여 이야기 중심의 글로 작성했습니다. 

원래는 Life Cycle과 관련한 이야기도 풀어내고 싶었지만, 하나의 포스팅은 '하나의 주제'를 '짧은 호흡'으로 풀어내야 한다고 생각하기 때문에 Life Cycle과 관련된 이야기는 다음 포스팅을 통해 전달하도록 하겠습니다.

 

추가적으로 이번 포스팅에선 다루지 않았지만 React에는 보다 다양한 Built-in React Hooks가 존재합니다. 자세히 알아보고 싶으신 분은 아래의 링크를 참고해주세요.

https://react.dev/reference/react

 

Built-in React Hooks – React

The library for web and native user interfaces

react.dev

 

본 포스팅을 준비하며 React Hooks 도입 이전에 개발자들이 겪었던 고충들에 대해 알아볼 수 있었습니다. 특히 React Hooks가 처음 등장했을 당시에 여러 개발자들이 이 혁신적인 방법에 대해 앞다투어 소개하며 커뮤니티가 활성화되었던 걸 보면 우리가 당연히 사용하는 React Hooks가 수많은 개발자들의 노력의 산물이라는 점을 알 수 있었습니다. 

 

지금은 React Hooks가 어느정도 주류에 자리잡았지만, 몇몇 Hook들은 개선 관련 논의가 현재 진행되고 있습니다. 이번 기회에 React Hooks와 관련한 정리를 진행하며 React Hooks의 향후 전망에 대해서 조사해보시는건 어떨까요?

저도 기회가 된다면 이와 관련한 포스팅을 업로드 할 수 있도록 노력하겠습니다.

 

 

참고

https://stackoverflow.blog/2021/10/20/why-hooks-are-the-best-thing-to-happen-to-react/?source=post_page-----d400cbc203a4-------------------------------- 

 

Why hooks are the best thing to happen to React - Stack Overflow

October 20, 2021Why hooks are the best thing to happen to ReactOriginally, React mainly used class components, which can be strenuous at times as you always had to switch between classes, higher-order components, and render props. With React hooks, you can

stackoverflow.blog

https://choijying21.tistory.com/60

 

[React] 리액트 훅에 대해 알아보기(React Hooks란?)

1. React Hooks란? 리액트 훅은 리액트 클래스형 컴포넌트에서 이용하던 코드를 작성할 필요없이 함수형 컴포넌트에서 다양한 기능을 사용할 수 있게 만들어준 라이브러리라고 할 수 있는데 React 16.

choijying21.tistory.com

https://minjung-jeon.github.io/React-Hook/

 

[React] React Hooks 개념 및 기초 | MINJUNG

React Hook 개념 및 기초(useState, useEffect)

minjung-jeon.github.io

https://www.w3schools.com/react/react_hooks.asp

 

React Hooks

W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.

www.w3schools.com

profile

Lambda

@Lamue

포스팅이 좋았다면 "공감❤️" 또는 "구독👍🏻" 해주세요!