[React] Hook 정리

박창조·2022년 1월 19일

React

목록 보기
3/9
post-thumbnail

React를 처음 공부하면서 배운내용을 정리 해보았습니다. 처음 공부하여 정리한 것이라 혹시라도 틀린 부분이 있다면 댓글로 친절히 남겨주시면 감사하겠습니다!!

0. Hook..?

Hook은 함수형 컴포넌트에서 생기는 아래 문제들을 해결하기 위해서 개발한 함수입니다.

  1. 컴포넌트 사이에서 상태로직을 재사용하기 어렵다.
  2. 복잡한 컴포넌트들을 이해하기 어렵다.
  3. Class 컴포넌트의 복잡한 문법은 많은 문제들을 가져왔다.

이러한 문제들을 해결하기 위해서 함수형 컴포넌트에서는 할 수 없었던 기능 (State, LifeCycle 등) 들을 Hook 통해 사용할 수 있게 되었습니다.


Hook의 규칙

Hook을 사용할 때는 두 가지 규칙을 준수해야합니다.

첫번째, 최상위에서만 Hook을 호출해야 합니다.

반복문, 조건문 혹은 중첩된 함수 내에서 Hook을 호출하지 않고, 항상 컴포넌트트 최상위에서 Hook을 호출해야 합니다. 컴포넌트가 렌더링 될 때마다 항상 동일한 순서로 Hook이 호출 되게 하기 위해서는 이 규칙을 지켜야 합니다.

두번째, React 함수 내에서만 Hook을 호출해야 합니다.

React 공식 문서에 올라와 있는 글을 참고하였습니다.


1️⃣ useState

설명

import React {useState} from 'react'

const [state, setState] = useState();

useState()는 함수형 컴포넌트안에서 state를 사용 할 수 있게 해주는 친구입니다. useState()는 새로 추가된 배열구조분해(구조분해할당) 문법으로 선언할 수있는데, (state변수)와 (state 변수를 업데이트 해주는 함수) 두가지를 반환합니다.

특징

  • 하나의 컴포넌트 내에서 여러개의 State Hook을 사용할 수 있다.
  • 배열 구조분해(destructuring) 문법을 사용하기 때문에 서로 구분되는 변수명을 사용 할 수 있다.
  • state업데이트 함수는 이벤트핸들러 안에서 비동기적으로 실행된다.
  • state에 객체나 배열 등의 데이터가 아니라 함수를 전달하면 이전생애의 state를 가져올 수 있다.
  • setState()에서 이전생애 상태(prevState)를 인자로 받는 callback 함수를 사용할 수 있습니다. -> 자세한 내용 다른 글에서

그리고..

state에 관해서 나중에 따로 정리해야할 것 같다. state는 처음에는 그저 변수인듯 생각했었는데, 익숙해지고 사용하는 지금은 정말 복잡한 친구라는 생각이 든다. state가 가지는 불변성이라던가 최신의 상태를 보여주지 못한다던가 사용하면서 경험한 오류들을 따로 정리해서 올리겠습니다...


2️⃣ useEffect

설명

import React {useEffect} from 'react'

useEffect(()=>{
  ...
}, [])

useEffect는 함수 컴포넌트 내에서 LifeCycle을 사용 할 수 있게 해주는 Hook입니다.

클래스 컴포넌트에서 사용되는 componentDidMount, componentDidUpdate, componentWillUnmount들과 같은 목적으로 사용되는데, 이것들을 useEffect라는 하나의 함수로 제공됩니다.

useEffect는 "effect함수"와, 의존 값이 들어있는 "배열(deps)"을 인자로 받습니다.

useEffect에서는 cleanup()(useEffect를 정리해주는 함수)를 return 할 수 있습니다. cleanup()는 컴포넌트가 언마운트 될 때 호출되는 함수 입니다.


📝 사용방법

useEffect는 기본적으로 렌더링이 될 때 마다 실행이 됩니다.

컴포넌트가 마운트 되었을때만 사용하기

만약 컴포넌트가 처음 렌더링 되었을때 (마운트 되었을 때) 만 실행하고 업데이트할 필요가 없는 경우에는 두번째 인자인 deps 배열을 비어있는 상태로 넣어주면 된다용~

useEffect(()=>{
  console.log('effect');
}, [])

특정 값이 업데이트 될 때만 실행하고 싶을 때

이때는 간편하게 두번째 인자인 deps 배열안에 해당 값을 넣어주면 된다용~
(배열안에 여러 값이 들어갔을 때 하나라도 변화가 감지되면 callback함수가 실행된다!)

useEffect(()=>{
  console.log('effect');
}, [name])

clean-up 함수 사용

컴포넌트가 언마운트되기 전이나, 업데이트가 되기 직전에 어떠한 작업을 수행 하고 싶을 때 Clean-up함수를 반환해 주면 된다.

컴포넌트가 나타날 때 effect 함수가 실행되고, 언마운트 될때 clean-up함수가 나타난다.

useEffect(()=>{
  console.log('effect');
  return () => {
    console.log('clean up')
  }
}, [])

🧸 특징 & 팁 정리

  • useEffect 안에서 사용하는 state나 props가 있다면, deps배열에 해당하는 값을 넣어줘야합니다. 만약 넣지 않는다면 useEffect에 등록한 함수가 실행 될 때 최신 props/상태를 가르키지 않게 된다.
  • 마운트 시 주로 하는 작업
    - props로 받은 값을 컴포넌트의 상태로 설정
    • 외부 API 요청
    • 라이브러리 사용
    • setInterval을 통한 반복작업, setTimeout을 통한 작업 예약
  • 언마운트 시에 하는 작업
    - setInterval, setTimeout 을 사용하여 등록한 작업들 clear 하기(clearInterval, clearTimeout)
    • 라이브러리 인스턴스 제거

3️⃣ context API (useContext)

설명

import react, {creacContext, useContext} from 'react';

function App () {
  const ThemeContext = createContext("값"); //비어있어도됨

  return (
    <ThemeContext.Provider>
   		<ToolBar value="dark" />
    </ThemeContext.Provider>
  )
}

function ToolBar(){
  const theme = useContext(ThemeContext)
}

Context는 props를 좀 더 효율적으로 사용할 수 있도록 도와주는 API입니다.
어떤 컴포넌트에서 자식 컴포넌트에게 props를 전달해줄 때 깊이가 얕으면 props를 이용하기 힘들지 않겠지만, 자식의 자식의 자식으로 계속해서 props를 전달해줘야 할 때 깊이가 깊어질 수 록 props를 다루기가 힘들어집니다.

Context는 자식에게 props를 일일히 전달해주지 않고도 전역적으로 변수나 함수를 사용 할 수 있게 해줍니다.

🔹 선언 - CreactContext

const ThemeContext = React.createContext(defaultValue);

CreactContext는 Context 객체를 생성해주는 API입니다. 이렇게 생성된 Context 객체는 3가지의 값을 기본적으로 반환해 줍니다.

1. Provider

<Context.Provider value={/*어떤 값*/}>
  {...}
</Context.Provider>

Provider는 해당 Context를 사용하고자 하는 컴포넌트들에게 구독을 할 수 있게 해주는 컴포넌트 입니다. Provider는 사용하고자 하는 컴포넌트들의 가장위에서 감싸지도록 하여 사용되는데, props로 전달되는 value를 Context객체를 이용하고자 하는 구독자 컴포넌트에게 값을 전달해 줍니다.

참고로 value로 전달되는 값으로는 객체를 보내면 문제가 될 수 있습니다.
자세한 내용 -> 주의사항

2. Consumer

<Context.Consumer>
  {(value) => {
    <h1>{value}</h1>
  }}
</Context.Consumer>

Consumer은 구독자 컴포넌트 입니다. 선언되어있는 Context 객체를 사용하기 위해서 사용하는 컴포넌트 입니다.

일반적으로 함수형 컴포넌트에서는 이것보다 추후 나올 useContext()를 더 많이 이용하기 때문에 잠시 건너 뛰고 추후 다시 작성하도록 하겠습니다.

3. displayName

Context.displayName = "어떤 이름"

displayName으로 문자열을 지정해 줄 수 있는데, 이 지정된 값은 React Dev Tool(개발자 도구)에 표시됩니다.

"에?? 그래서 어떻게 되는건데?"

적용 전적용 후

➕ Context 객체를 선언할 때 인자로 사용되는 "defaultValue"는 Context에 접근했을 때 기본적으로 가져오는 값입니다. 하지만 해당 Context를 사용하는 구독자 컴포넌트에서Provider를 찾았을 때는 props값인 value 값을 가져온다고 이야기 했는데, "defaultValue"값은 Provider를 찾지 못했을 경우에 대신 가져와 사용합니다.

📝 사용 - useContext

const useThemeContext = useContext(ThemeContext);

useContext 는 이미 만들어놓은 context 객체를 불러와 사용할 수 있게 해주는 Hook 입니다.

useContext의 인자로는 반드시 "Context 객체"만 들어올 수 있습니다. 이렇게 선언된 변수를 통해 가장 가까이에있는 Provider의 value를 가져오게 됩니다. 혹시나 찾는 Provider가 없을 경우, Context를 선언하면서 사용했던 "defaultValue" 값을 가져오게 됩니다.

🧸 특징 & 팁 정리

  • Context.Provider 아래에 또 다른 Context의 Provider가 올 수 있습니다. 이때는 하위의 Provider이 우선시 됩니다.

  • Context.Provider 하위에 context를 사용하는 컴포넌트는 Provider의 value로 가진 state가 변화할 때마다, 전부 렌더링 됩니다.
    ( 그래서 자주 사용하는 값이나 함수를 Context를 통해 넘겨준다면, 성능에 안좋을 수 있습니다.)

  • 주로 Context를 이용하는 경우

    • 변화가 거의 없는 값을 사용할 때
      ex) 로그인 데이터, 설정 내용, Theme, 장소, 언어 등등

4️⃣ useRef

설명

const refContainer = useRef(initialValue);

useRef는 .current 객체를 반환하는 Hook 함수입니다. .current 객체는 변경이 가능하고, 기본 값으로 선언할 때 useRef의 인자로 넣은 값이 들어가게 됩니다. 그리고 모든 생애주기에 걸쳐 반환된 값은 항상 유지가 되고, 값이 변경될 때마다 재렌더링이 일어나지 않습니다.

"모든 생애주기에 걸쳐 반환된 값이 항상 유지가 된다는 말과 재렌더링이 일어나지 않는다는 것은 무슨말일까요?"

이해하기 쉽게 useRef와 비슷하게 값을 저장하는 useStateconst변수3가지를 비교해보겠습니다.

함수형 컴포넌트이 재렌더링 될 때마다 const변수는 사라지고, 다시 생성되어 할당되고를 반복합니다. 당연하게 const변수는 생애주기에 걸쳐 값이 유지되지 않는 다는 것을 알 수 있죠. useState는 useRef와 마찬가지로 모든 생애주기에 걸쳐 값이 유지되는 것은 똑같습니다. 하지만, 앞서 알아본바와 같이 값이 변경될 때 마다 컴포넌트의 재렌더링이 일어난다는 것을 알 수 있습니다. useRef는 생각보다 특별한 변수 입니다.

언제 사용할까?

useRef는 특정 DOM을 제어하고 싶을 때 많이 사용합니다. 특정 HTML엘리먼트에 "ref"로 props를 전달해주면 해당 엘리먼트에 접근 할 수 있습니다. doucument.querySelector()와 비슷하게 동작한다고 생각하면 이해하기 쉽습니다.

/*useRef로 특정 엘리먼트에 접근하는 예시*/
const inputRef = useRef();
  ...
return (
  <input type="text" name="id" ref={inputRef}/>
)

위와 같이 사용하면 HTML엘리먼트에 접근해 DOM을 제어할 수 있게 됩니다.

이렇게 접근해서 보통 value 값을 가져와 사용하거나 변경 시킬 수 있고, focus()를 사용할 때 많이 사용하곤 합니다. 이것 외에도 특별한 엘리먼트들이 가지고 있는 DOM API를 제어하는데 사용합니다.

callback ref

forward ref

추후 추가 할 예정입니다.

🧸 특징 & 팁 정리

  • Ref의 값이 변경 될 때 렌더링이 일어나지 않습니다.

5️⃣ useMemo & useCallback

"메모제이션된 값을 반환한다."

메모이제이션(memoization) 은 컴퓨터 프로그램이 동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술이다. 동적 계획법의 핵심이 되는 기술이다.

먼저 이야기를 시작하기전에 우리가 기억해야 할 것이있습니다.

React에서는 부모컴포넌트가 렌더링 될 때, 자신의state나 부모로 부터 전달받은 props에 변경 사항이 있을 때 컴포넌트가 "렌더링"됩니다.

그리고 함수형 컴포넌트(이하FC)는 말 그대로 함수 입니다. 단순하게는 jsx를 반환해주는 함수입니다. 그렇기 때문에 컴포넌트가 렌더링 된다는 것은 함수를 재실행한다는 의미이기도 합니다. 함수가 재실행 되면 해당 함수 안에서 만들어진 선언문이나 또 다른 함수가 계속해서 새로 생성되고, 사라지기를 반복되어집니다.

FC의 크기가 작다면 크게 상관이 없겠지만 FC의 크기가 커지게 된다면 성능에 있어서 큰 영향을 주게 될 것입니다. 이때 특정 조건에서 발생하는 불필요한 렌더링을 조금이라도 방지해주는 역할을 하는 Hook들을 소개합니다.

uesMemo

자주 사용하는 함수의 을 메모제이션하고, 반환한다.

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

첫번째 인자로 함수가 들어가고, 두번째 인자로 의존성배열(deps)를 받는다.

useMemo는 첫번 째 인자로 받은 함수의 return값을 메모제이션 하고, 의존성배열에 들어가 있는 값이 변경될 때마다 함수의 return 값을 재계산 합니다.
의존성배열에 아무것도 전달이 되지 않은 경우에는 컴포넌트가 렌더링 될 때마다 재계산하게 됩니다.

useMemo는 여러 props값을 전달 받는 컴포넌트에서 하나의 변경 값 때문에 여러 값을 반환하는 함수의 재실행을 막아주거나, 여러 state가 있을 때도 사용합니다.

useCallback

자주 사용하는 함수를 메모제이션하고, 반환한다.

첫번째 인자로 함수가 들어가고, 두번째 인자로 의존성배열(deps)를 받는다.

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

useCallback은 useMemo와 비슷하게 동작합니다. 첫번째 인자로 함수를 받는 대신 함수 자체를 기억하고, 두번째 이자로 받은 의존성배열의 값이 변경될 때마다 재계산합니다.

(계속해서 새로운 인자를 받지 않는 함수)를 마운트 되었을 때만 사용할 수 있도록 메모제이션해서 기억할 수 있게 해줍니다. 전달받는 인자가 아에 없거나, 전달 받는 인자가 정해져 있을때 사용하면 좋습니다.

만약 하위 컴포넌트React.memo()로 최적화가 되어있고, 상위컴포넌트에서 props로 함수를 전달할 때 그 전달하는 함수를 useCallback으로 선언하면 성능관리하기 좋습니다.

주의할 점은, 함수안에서 사용하는 state나 props가 있다면 반드시 의존성배열에 포함해야한다든 것입니다. 그렇지 않는다면 함수안에서 참조하는 값이 최신값이라고 보장 할 수 없습니다.

주의할 점

useMemouseCallback는 분명 성능관리하게 도움을 주는 Hook입니다. 하지만 이것들은 메모리에 메모제이션해주는 함수 이기때문에 무분별하게 사용했을 때 오히려 역효과가 날 수 있습니다. 만약 사용할 때 사용되는 함수가 작은 로직을 담고 있다면 성능향상에는 미미한 효과가 있을 것이기 때문에 사용하지 않는 편이 좋을 수 있습니다. (불필요하게 shouldComponentUpdate를 발생 시키기 때문) 그러므로 사용할 때 이 함수에 Hook을 사용했을 때 확실한 성능효과를 볼 수 있을 때 사용하는 것이 좋을 것입니다.


6️⃣ useReducer

설명

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

"useState를 대신해서 사용할 수 있는 함수입니다."

useReducer는 useState()를 사용하면 생기는 복잡함과 불편함을 보완하고자 만들어진 Hook 입니다. 상태를 관리할 때 몇가지 state만을 사용한다면 관리하고 처리하기 편하겠지만, 어플리케이션의 크기가 커지고 이용하는 상태들이 많아진다면 코드가 많아지고 복잡해져 관리하기 힘들어질 것입니다. 이때 useReducer를 사용하면 상태관리를 좀 더 편하고 체계적으로 할 수 있습니다.

기본 요소

  • useReducer는 선언할 때 statedispatch함수를 받아옵니다.

  • 인자로 아래와 같은 reducer함수와 초기State값 그리고 init(옵션) 3가지를 가집니다.

function reducer (state, action){
  ...
  return newState
}

@ state : 현재 상태를 나타내는 있는 값
@ dispatch : 선언해 놓은 reducer함수를 통해 미리 만들어 놓은 action 값을 토대로 state값을 어떻게 변화 시킬지 명령을 보내주는 함수 Ex) dispatch(action)

@ reducer : state와 state를 변화시킬 동작을 가지고 있는 함수
@ InitialState : 초기 상태 값
@ Init (옵션) :


📝 사용방법

처음에 이걸 어떻게 이해하지..라는 생각이 들었었는데, 간단한 예제를 작성해보면서 어떻게 동작하고, 사용하면 좋을지를 스스로 알게 되었습니다.

그래서 먼저 제가 간단하게 만들어본 예제를 보면서 설명해 드리겠습니다.

import React, {useReducer} from 'react';

/*useReducer에 인자로 들어갈 reducer함수*/
const countReducer = (state, action) => {
  switch (action) {
    case 'increment':
      return (state = state + 1)
    case 'decrement':
      return (state = state - 1)
    case 'reset':
      return (state = initState)
    default:
      throw new Error()
  }
}

/*useReducer에 인자로 들어갈 초기 state 값*/
const initState = 0;

const App = () => {
  const [count, dispatch] = useReducer(countReducer, initState)
  return (
    <>
      Count : {count}
      <button onClick={() => dispatch('increment')}>+</button>
      <button onClick={() => dispatch('decrement')}>-</button>
      <button onClick={() => dispatch('reset')}>초기화</button>
    </>
  )
}

export default App

동작되는 순서를 살펴보면 useReducer()의 원리를 이해 할 수 있을 것입니다.

먼저, 버튼을 클릭 했을 때 dispatch 함수를 통해 action을 전달합니다.

이때 action 값으로 어떠한 값이 들어가도 상관 없지만 보통은 객체 형태로 { type: "액션값"}을 사용하는 듯 합니다. (InitialState 값도 맘대로 설정하기 나름입니다.)

두번째로, dispatch를 통해 countReducer 함수에 action이 전달 됩니다. countReducer 함수는 전달받은 action에 맞는 로직을 실행하여 새로운 State 값을 반환하게 되어집니다.

생각보다 쉽지 않나요? 다른 Hook api 들과 다르게 조금 복잡하지만, 잘 활용하면 어떤 것 보다 강력한 함수라는 생각이 듭니다.


🧸 특징 & 팁 정리

  • useReducer는 다수의 하윗값을 포함하는 복잡한 정적 로직을 만드는 경우 사용하면 좋습니다.

  • 새로운 state가 이전 state에 의존적인 경우에 사용하면 좋습니다.

  • state 관련 로직을 컴포넌트와 분리하여 작성할 수 있엉 가독성 측면에서 좋습니다.

  • 비동기적으로 동작합니다.

  • Context API와 함께 사용하면 활용도가 높습니다.

  • 문법이 Redux와 유사한 점이 많습니다.


7️⃣ Custom Hook

"컴포넌트에서 어떤 로직을 함수로 만들어 자신만의 Hook으로 만들 수 있습니다."

custom Hook은 사실 별로 특별한 점은 없습니다. 우리가 어떠한 기능을 담고 있는 함수를 만드는 것이라고 생각하면 편합니다. 우리가 코드를 짜다보면 반복적으로 사용되는 로직들이 많이 생길 것입니다. 이러한 로직들을 hook으로 만들면 재사용성을 높일 수 있고, 유지보수성이 높아집니다. 우리가 컴포넌트를 재사용 하듯 로직들을 재사용하기위해 Hook을 만들어 사용하는 것이 코드를 관리하기에 유용합니다.

React에서 custom Hook을 사용하기 위한 규칙이 있습니다.

  1. Hook 함수의 이름은 "use"로 시작해야 합니다.
  2. 처음 시작할 때 이야기 했던 Hook의 규칙을 지켜야 합니다.

추가로 우리가 만든 custom Hook은 앞서 설명했던 바 함수로서 동작하기 때문에 여러 컴포넌트에서 중복적으로 사용한다고 해도 Hook안에서 정의했던 state 들도 서로 종속되지 않습니다.

앞서 소개했던 Hook들은 React에서 기본적으로 제공해주는 것이지만, useMemo, useReducer 같은 Hook들을 우리도 충분히 만들어 사용할 수 있습니다. 그러니 너무 쫄지 말고, 필요해 보이는 것이 있다면 스스로 만들어 사용하는 것도 좋을 것 같습니다.
(저 스스로에게 하는 말입니다..ㅎㅎ)

마지막으로 React 공식 문서에 있는 에제인 custom hook으로 useReducer를 구현해보겠습니다. (설명은 생략하겠습니다.)

import { useState } from 'react'

const useCustomReducer = (reducer, initalState) => {
  const [state, setState] = useState(initalState)

  const dispatch = action => {
    const newState = reducer(state, action)
    setState(newState)
  }

  return [state, dispatch]
}

import React from 'react';

/*useReducer에 인자로 들어갈 reducer함수*/
const countReducer = (state, action) => {
  switch (action) {
    case 'increment':
      return (state = state + 1)
    case 'decrement':
      return (state = state - 1)
    case 'reset':
      return (state = initState)
    default:
      throw new Error()
  }
}

/*useReducer에 인자로 들어갈 초기 state 값*/
const initState = 0;

const App = () => {
  const [count, dispatch] = useCustomReducer(countReducer, initState)
  
  return (
    <>
      Count : {count}
      <button onClick={() => dispatch('increment')}>+</button>
      <button onClick={() => dispatch('decrement')}>-</button>
      <button onClick={() => dispatch('reset')}>초기화</button>
    </>
  )
}

export default App

+ Referece

React 공식 문서
useEffect 완벽가이드
irrationnelle
기억보다 기록을
이화랑 블로그
Tecoble

profile
사랑을 꿈꾸는 냄새나는 개발자 입니다 :)

0개의 댓글