[React] Hooks 알아보기(useState, useEffect, useRef, useMemo , useCallback, useReducer, useContext)

Alicia·2023년 8월 12일
2

AI_Web_nipa

목록 보기
16/31

목차

각 hook별

WHAT (무엇인가? 어떤 기능이지?)
WHY (왜 쓰는건가?)
WHEN (언제 쓰는건가?)
HOW (어떻게 쓰는거지?)

마지막 차이점 표로 정리

useState

state : 계속해서 변화하는 특정 상태, 상태에 따라 각각 다른 동작을 함

✅ what

컴포넌트 가변적인 상태를 생성하고 업데이트
함수형 컴포넌트에서도 클래스 컴포넌트와 유사한 기능을 사용할 수 있게 해줍니다.

💻 why

React 컴포넌트에서 상태(state)를 관리하기 위해서입니다. 이전에는 클래스 컴포넌트에서 state를 사용하여 컴포넌트의 내부 상태를 저장하고 변경하는 것이 일반적이었습니다. 그러나 함수형 컴포넌트에서는 클래스 컴포넌트처럼 state를 직접 사용하는 것이 불가능했습니다. 이런 문제를 해결하고 간결하고 유지보수가 용이한 코드를 작성하기 위해 useState가 도입되었습니다.

🔆 when

컴포넌트 내부 상태 관리 동적인 UI 업데이트 랜더링 관리 컴포넌트 간 데이터 공유 복잡한 컴포넌트 상태 관리 컴포넌트 최적화

❓ how

useState는 배열을 반환하며, 배열의 첫 번째 요소에는 현재 상태값이 들어가고 두 번째 요소에는 상태를 변경하는 함수가 들어갑니다.

import React, { useState } from 'react';  //★ 꼭 import 할것

function Counter() {

  // 1. 상태(state) 초기값 설정
  /* useState(0)을 호출하여 count라는 상태와 setCount라는 상태 업데이트 함수를 생성합니다. 초기값으로 
  0을 설정했습니다.
  */
  
  const [count, setCount] = useState(0);

  // 2. 상태 업데이트 함수 사용
  const increment = () => {
  //상태 업데이트 함수인 setCount를 사용하여 count 상태를 업데이트할 수 있습니다.
    setCount(count + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

export default Counter;




useEffect

✅ what

React 컴포넌트의 생명주기와 관련된 기능을 제공합니다. 컴포넌트가 렌더링된 후에 특정 작업을 수행하거나, 컴포넌트가 언마운트(화면에서 제거) 되기 전에 정리 작업을 수행할 수 있게 해줍니다.
한마디로 렌더링 시점을 기반으로 리소스를 정리하거나 이벤트 리스너를 제거하고 컴포넌트를 안정적으로 유지하는 훅이라고 보시면 됩니다.

💻 why

1) 비동기 작업 관리: API 호출, 데이터 불러오기 등의 비동기 작업을 수행할 때 사용됩니다. 컴포넌트가 렌더링된 후에 비동기 작업을 수행하려면 useEffect를 활용합니다.

2) 클린업 작업: 컴포넌트가 언마운트되기 전에 정리 작업을 수행해야 하는 경우 useEffect를 활용하여 메모리 누수를 방지하고 리소스를 확보합니다.

3) 렌더링 관련 작업: 컴포넌트가 렌더링된 후에 DOM 조작이나 스크롤 위치 설정 등을 하고 싶을 때 사용됩니다.

🔆 when

렌더링 이후 작업 API 호출, DOM 조작, 이벤트 리스너 등을 초기화 상태 업데이트 시 특정작업 수행 의존성 배열 사용

❓ how

import React, { useState, useEffect } from "react";

function App() {
  const [clickCount, setClickCount] = useState(0);

  // 클릭 횟수가 변경될 때마다 문서의 제목을 업데이트
  useEffect(() => {
    document.title = `클릭 횟수: ${clickCount}`;
  }, [clickCount]);

  const handleClick = () => {
    setClickCount(clickCount + 1);
  };

  return (
    <div>
      <h1>클릭 횟수: {clickCount}</h1>
      <button onClick={handleClick}>클릭하기</button>
    </div>
  );
}

export default App;




useRef

✅ what

React 함수 컴포넌트에서 DOM 요소 또는 컴포넌트 인스턴스를 참조하는 데 사용되는 훅입니다. 이 훅을 사용하면 DOM 요소에 직접 접근하거나 컴포넌트의 인스턴스 변수를 생성할 수 있습니다.
.
.
.말이 좀 어려운데 쉽게 설명하자면 이 도구를 사용하면 컴포넌트 안에서 HTML 요소(태그)를 가리키거나, 컴포넌트 내에서 사용할 변수를 만들 수 있어요.

사실상 이 도구는 "매직 박스"와 같습니다. 컴포넌트가 이전에 어떤 일을 했었는지를 기억하고, 그런 일들과 관련된 정보를 가지고 있게 해줍니다.

💻 why

1) DOM 요소에 직접 접근해야 할 때 사용합니다.
2) 컴포넌트의 렌더링과 관련 없이 상태를 유지하고 싶을 때 사용합니다.
3) 이전 상태 또는 프로퍼티의 값을 기억하고 싶을 때 사용합니다.
4) 실제 DOM에 접근해야 하지만 상태 변경에 따른 불필요한 리렌더링을 방지하고 싶을 때 사용합니다.

🔆 when

DOM 조작이나 측정 이전 값과 현재 값의 차이를 비교하거나 기록 외부 라이브러리와 통합

웹사이트를 구축할 때 사용자가 페이지를 이동할 때 페이지의 스크롤 위치를 기억해 그 위치로 이동하게하고 싶다고 가정해 보자

❓ how

예시 : 스크롤 위치 기억하기

import React, { useRef, useEffect } from "react";

function ScrollRemember() {
  const scrollPosition = useRef(0); // scroll position 저장

  // 언마운트 될때  scroll position 저장
  useEffect(() => {
    return () => {
      scrollPosition.current = window.scrollY; // Save the scroll position
    };
  }, []);

  // 마운트 될 때 scroll position 복원 
  useEffect(() => {
    window.scrollTo(0, scrollPosition.current); // Restore the scroll position
  }, []);

  return (
    <div>
      <p>Scroll down and click the button.</p>
      <button onClick={() => window.scrollTo(0, 0)}>Scroll to top</button>
    </div>
  );
}

export default ScrollRemember;

이 예시에서 scrollPosition은 스크롤 위치를 저장하는 참조입니다. 컴포넌트가 마운트되거나 언마운트될 때, 이전 스크롤 위치를 저장하고, 새로운 페이지로 이동할 때 그 저장된 위치로 스크롤을 이동시킵니다.



근데 여기서 의아한점.....

useState도 상태 관리로 데이터를 저장해놓는 훅 아닌가....?!
좀 헷갈린 관계로 여기서 useState와 useRef 차이점 깔끔하게 정리하고 갑시다!

값 변경에 대한 리렌더링:

  • useState : 상태가 변경되면 컴포넌트가 리렌더링됩니다. 이전값과 현재값의 차이를 비교하는 것은 이전 렌더링과 현재 렌더링 사이에서만 가능합니다.
  • useRef : useRef로 관리하는 값의 변경은 컴포넌트의 리렌더링을 트리거하지 않습니다. 따라서 이전값과 현재값의 차이를 비교하려면 컴포넌트의 렌더링 사이에도 가능합니다.

값의 변경과 기록:

  • useState: 새로운 값을 기반으로 새로운 상태를 생성하고 컴포넌트를 다시 렌더링합니다. 이는 보통 컴포넌트의 UI를 업데이트하기 위해 사용됩니다.
  • useRef: useRef로 관리하는 값은 값이 변경되더라도 컴포넌트가 다시 렌더링되지 않습니다. useRef는 보통 값의 기록이나 DOM 요소의 참조 등에 사용됩니다. useRef의 값이 변경되어도 컴포넌트의 리렌더링은 발생하지 않습니다.

렌더링에 영향을 주지 않는 값:

  • useState: 상태값의 변경은 컴포넌트의 리렌더링을 유발합니다. 이는 주로 컴포넌트의 상태나 UI를 관리할 때 사용됩니다.
  • useRef: useRef로 관리하는 값은 변경되어도 컴포넌트의 리렌더링에 영향을 주지 않습니다. 주로 DOM 요소의 참조나 값의 기록에 사용됩니다.

따라서, useState는 주로 컴포넌트의 상태 및 UI 업데이트에 사용되고, useRef는 주로 값의 기록, DOM 요소의 참조 등에 사용됩니다. 둘의 선택은 상황에 따라 다르며, 역할과 동작을 이해하여 올바르게 활용하는 것이 중요합니다.




useMemo

✅ what

계산 비용이 높은 함수의 결과 값을 기억하고, 동일한 인자로 재실행될 때 이전에 계산한 값을 반환(재사용)하여 성능을 최적화하는 역할을 합니다.

  • 계산비용 : 컴퓨터가 수행하는 작업 중에서 시간과 리소스가 많이 소비되는 작업을 말합니다. 이런 작업을 수행하는 함수는 실행될 때 많은 연산이 필요하거나 데이터를 많이 처리하게 됩니다.

💻 why

React 컴포넌트 내에서 렌더링이 발생할 때마다 계산 비용이 높은 함수를 호출하면 성능 문제가 발생할 수 있습니다. 이러한 계산이 반복되는 것을 방지하고자 할 때 useMemo를 사용합니다. useMemo를 사용하면 이전에 계산한 결과 값을 캐싱하여 같은 인자가 전달될 때 다시 계산하는 대신 이전 값을 반환합니다.

🔆 when

렌더링 성능 최적화 컴포넌트의 리렌더링이 빈번하거나 성능에 영향을 미치는 계산존재 중복 계산을 방지

❓ how


예제 1) 복잡한 계산

import React, { useState, useMemo } from "react";

function ExpensiveCalculation({ value }) {
  // 계산 비용이 높은 함수
  const calculateResult = (input) => {
    console.log("Calculating...");
    // 여기에서 복잡한 계산이나 데이터 처리를 수행한다고 가정
    return input * 2;
  };

  // useMemo를 사용하여 계산 비용이 높은 함수의 결과 값을 캐싱
  const cachedResult = useMemo(() => calculateResult(value), [value]);

  return <div>Result: {cachedResult}</div>;
}

function App() {
  const [inputValue, setInputValue] = useState(5);

  return (
    <div>
      <input
        type="number"
        value={inputValue}
        onChange={(e) => setInputValue(Number(e.target.value))}
      />
      <ExpensiveCalculation value={inputValue} />
    </div>
  );
}

export default App;

계산 결과 값을 캐싱하여, 같은 인자로 여러 번 호출될 때마다 동일한 계산을 반복하지 않고 캐시된 값을 사용합니다.

예제2 ) 리렌더링 되지 않는 결과값

// useMemo
import React, { useState, useMemo } from "react";

const Form = ({ listText, currentText, setListText, setCurrentText }) => {
  console.log("Form is rendered");
  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        setListText([...listText, currentText]);
      }}>
      <input
        name="text"
        onChange={(e) => {
          setCurrentText(e.target.value);
        }}
      />
      <input type="submit" />
      
      /*submit을 누를때만(결과값 도출될때) 리렌더링. 
      onChange의 currentText와는 상관하지 x */
      
      {useMemo(() => {
        console.log("Content is rendered");
        return (
          <ul>
            {listText.map((item, index) => (
              <li key={index}>{item}</li>
            ))}
          </ul>
        );
      }, [listText])}
    </form>
  );
};

const App = () => {
  console.log("App is rendered");
  const [currentText, setCurrentText] = useState("");
  const [listText, setListText] = useState([]);

  return (
    <>
      <Form setCurrentText={setCurrentText} setListText={setListText} 
      currentText={currentText} listText={listText} />
    </>
  );
};

export default App;



여기서 또 의문점.. useRef도 내가 지정한 인스턴스변수의 값을 저장하고 부를때 쓰이는건데 둘다 캐싱의 기능이 있지 않은가?

.
.
.맞습니다. useMemo와 useRef는 둘 다 캐싱을 활용한다는 점에서는 맞지만, useMemo는 주로 계산 비용을 줄이기 위해, useRef는 주로 값의 변화를 유지하기 위해 사용됩니다. 이 때문에 어떤 상황에서 어떤 훅을 선택하여 사용할지 잘 판단해야 합니다.




useCallback

✅ what

React의 최적화 기능 중 하나로, 함수 재생성을 방지하고 렌더링 성능을 최적화하는 데 도움을 주는 훅입니다

💻 why

React에서는 컴포넌트의 상태나 프롭스가 변경될 때 컴포넌트가 다시 렌더링됩니다. 이로 인해 함수들이 컴포넌트 내에서 불필요하게 재생성될 수 있는데, 이 함수들이 실제로 변경될 필요가 없더라도 말이죠. useCallback 훅은 이러한 문제를 해결하기 위해 함수들을 메모이제이션하여, 의존성이 변경될 때만 새로 생성되도록 보장합니다.

🔆 when

자식 컴포넌트에 콜백 함수 전달 이벤트 핸들러 최적화 함수 생성을 제어 컴포넌트의 효율성을 향상

❓ how

import React, { useState, useCallback } from 'react';

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

  // handleClick 함수가 매 렌더링마다 새로운 함수 인스턴스를 생성합니다.
  // 따라서 매번 버튼을 클릭할 때마다 Counter 컴포넌트가 재렌더링됩니다.
  // const handleClick = () => {
  //   setCount(count + 1);
  // };

  // useCallback을 사용하여 함수를 메모이제이션합니다.
  // 의존성 배열이 비어 있으므로 함수는 처음 한 번만 생성됩니다.
  const handleClick = useCallback(() => {
    setCount(prevCount => prevCount + 1);
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      {/* 매번 버튼을 클릭해도 함수는 재생성되지 않습니다 */}
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

export default CounterButton;

useMemo, useEffect와 차이점
useMemo는 계산 비용이 높은 값의 캐싱을 위해 사용.

useEffect는 부작용 관리와 데이터 동기화를 위해 사용.

useCallback는 함수 메모이제이션을 통한 자식 컴포넌트 최적화나 불필요한 리렌더링 방지를 위해 사용.




useReducer

✅ what

컴포넌트의 상태 관리를 위해 도와주는 리액트 훅 중 하나입니다. 이 훅은 상태를 관리하고 업데이트하는 데에 사용됩니다. (외부와 연결성, 업그레이드)

💻 why

상태 관리가 복잡하고 다양한 액션에 따라 다양한 상태 변화가 필요한 경우에 주로 사용됩니다. useState보다 좀 더 복잡한 상태 관리에 유용하며, 리덕스와 같은 상태 관리 라이브러리의 원리를 재사용할 수 있게 해줍니다.

🔆 when

컴포넌트 상태 복잡 여러 액션에 의해 변경 여러 컴포넌트 간의 공유되는 로직 상태 업데이트 로직을 컴포넌트 밖으로 분리

❓ how

필요한 부분 2가지

  • 리듀서 함수는 두 개의 인수를 전달받는다. : 상태, 액션

  • 액션이란? 상태를 '어떻게 해달라'는 내용이 담긴 객체

  • 리듀서는 로직을 수행한 결과를 반환하고, 이는 새로운 상태가 된다!

  • 디스패치 : 리듀서에게 액션을 전달하는 함수

import React, { useReducer } from 'react';

// 리듀서 함수
const reducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'DECREMENT':
      return { ...state, count: state.count - 1 };
    default:
      return state;
  }
};

function Counter() {
  // useReducer를 통해 상태와 디스패치 함수를 받아옴
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>Increment</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>Decrement</button>
    </div>
  );
}

export default Counter;




useContext

✅ what

컴포넌트 간에 상태를 공유하고 전달하기 위해 사용되는 기능입니다. props를 통해 상위 컴포넌트에서 하위 컴포넌트로 값을 전달하는 것 대신, 컴포넌트 트리 안에서 상태를 공유하는 더 간단한 방법을 제공합니다.

💻 why

props로 여러 단계를 거치지 않고도 값을 전달할 수 있습니다. 이는 코드의 가독성을 높이고 중복을 줄이는 데에 도움을 줍니다.

🔆 when

컴포넌트 트리 안 중첩된 컴포넌트들 사이에서 값을 전달 상태 관리 라이브러리 사용 X 컴포넌트 간에 데이터를 공유

❓ how

// App.js

import React, { createContext } from "react";
import Component1 from "./Component1";
import Component2 from "./Component2";

const MyContext = createContext();

function App() {

  const valueToPass = "Hello from App!";

  return (
    <MyContext.Provider value={valueToPass}>
      <Component1 />
      <Component2 />
    </MyContext.Provider>
  );
}

export default App;

// Component1.js
import React from "react";
import SubComponent1 from "./SubComponent1";

function Component1() {
  return <SubComponent1 />;
}

export default Component1;

// SubComponent1.js
import React, { useContext } from "react";
import MyContext from "./App";

function SubComponent1() {
  const value = useContext(MyContext);

  return <p>{value}</p>;
}

export default SubComponent1;

App 컴포넌트 안에 Component1과 Component2가 있고, Component1 안에는 subComponent1이 있다면, App 컴포넌트에서 useContext를 사용하여 subComponent1까지 한 번에 값을 전달할 수 있습니다. 이렇게 하면 중간 컴포넌트인 Component1을 건너뛰고 바로 subComponent1로 값을 전달할 수 있게 됩니다.


⭐총정리

useStateuseEffectuseRefuseMemouseCallbackuseReduceruseContext
역할/목적상태 관리 및 업데이트부작용(네트워크 요청, DOM 조작 등) 관리DOM 요소 또는 컴포넌트 인스턴스 참조계산 비용이 높은 연산 결과 캐싱함수를 메모이제이션하여 재사용복잡한 상태 로직을 분리하여 관리상위 컴포넌트에서 하위 컴포넌트로 데이터 전달
주요 사용 시점컴포넌트 내부 상태 업데이트 시 사용렌더링 이후 데이터를 가져오거나 조작할 때DOM 요소나 컴포넌트 인스턴스 참조 시계산 비용이 높은 결과를 재사용할 때렌더링 성능 최적화 및 콜백 함수 생성 시상태 관리가 복잡하고 예측 가능한 상태 업데이트 시여러 컴포넌트에서 동일한 데이터 공유 시
역할/목적컴포넌트 내에서 상태를 저장하고 관리하는 데 사용컴포넌트 렌더링 이후 부작용(사이드 이펙트) 관리에 사용변경되지 않는 변수를 유지하거나 참조하기 위해 사용특정 값이 변경될 때만 결과 값을 다시 계산하고 캐싱하여 사용함수를 새로 생성하지 않고 재사용하여 렌더링 성능을 개선복잡한 상태 로직을 분리하여 관리하고 액션에 따라 상태 업데이트상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달하고 공유하는 데 사용

*본 후기는 정보통신산업진흥원(NIPA)에서 주관하는 학습 기록으로 작성 되었습니다.

0개의 댓글