⚛️ React 공부/기초 문법

ReactJS 숙련: Hooks (useState, useEffect, useRef, useContext)

seheej 2024. 8. 19. 19:21

1. useState

정의

useState는 리액트의 기본 훅 중 하나로, 함수 컴포넌트에서 가변적인 상태를 관리할 수 있게 해줌

useState를 사용하면 컴포넌트 내에서 상태를 정의하고, 그 상태를 업데이트할 수 있음

 

setState를 사용하는 두 가지 방법: 일반적인 값 업데이트와 함수형 업데이트 방식

 

일반 업데이트

  • 여러 번 호출해도 마지막 호출만 적용 (각각 실행되는 것이 아니라 배치 업데이트로 처리함) => set을 동일요청으로 판단하여 한 번만 업데이트 함
  • Why? 불필요한 렌더링을 방지하기 위해서 (=렌더링 최적화를 위해)
import React from "react";
import { useState } from "react";

const App = () => {
  const [count, setCount] = useState(0);

return (
    <div>
    {count}
    <button
        onClick={() => {
          // 1. useState 일반 사용법
          // 아래 setCount는 3이 증가하는 것이 아니라 1 증가
          setCount(count + 1);
          setCount(count + 1);
          setCount(count + 1);
     }}
    >
      Plus
    </button>
    );
};

export default App;

함수형 업데이트

  • 모든 호출이 순차적으로 적용
  • 장점: prop drilling을 예방할 수 있음
  • 언제 사용?
      • 현재 상태를 기반으로 새로운 상태를 계산하는 데 유용
      • 연속적인 상태 변경이 발생하는 경우 유용
const filterByAge = (minAge) => {
  setFilteredStudents(students);	// 초기화 후 필터
  setFilteredStudents(prev => prev.filter(student => student.age >= minAge));	// prev에는 students가 들어옴
}; 
  
import React from "react";
import { useState } from "react";

const App = () => {
  const [count, setCount] = useState(0);

return (
    <div>
    {count}
    <button
        onClick={() => {
          // 2. 함수형 업데이트
          // 아래 setCount는 + 3이 증가: 세 번을 동시에 명령을 내리게 되면, 이 명령을 모아 순차적으로 한 번씩 실행시킴
          setCount((prev) => prev + 1); // 최초 값 0 이 들어와 prev이 1이 됨
          setCount((prev) => prev + 1); // 다음 setCount에서 이전 prev값 1을 인자로 가져와 1+1 =2가됨
          setCount((prev) => prev + 1); // 2가 인자로 들어와 2+1 = 3이 리턴이 됨
        }}
      >
      Plus
    </button>
    );
};

export default App;

 

부모-자식 컴포넌트에서 state 변경

  • 부모 컴포넌트의 상태를 자식 컴포넌트에서 변경하려면, setState 함수를 props로 전달하는 것이 효율적
  • 함수형 업데이트 방식을 사용하면 불필요한 상태 전달을 줄일 수 있음
import React from "react";
import { useState } from "react";
import Child from "./components/Child";

const App = () => {
  const [count, setCount] = useState(0);
  
return (
    <div>
      <h1>여기는 부모 컴포넌트입니다.</h1>
      <span>현재 카운트: {count}</span>
      <Child setCount={setCount} />
    </div>
  );
};

export default App;
// src > components > Child.jsx
const Child = ({ count, setCount }) => {

  return (
    <div>
      <h3>여기는 자식 컴포넌트 입니다.</h3>
      <button
        onClick={() => {
          	// 1. 일반 업데이트 방식
            setCount(count + 1);
        }}
      >
        Count 1 증가
      </button>
    </div>
  );
};

export default Child;
// src > components > Child.jsx
const Child = ({ setCount }) => {

  return (
    <div>
      <h3>여기는 자식 컴포넌트 입니다.</h3>
      <button
        onClick={() => {
          // 함수형 컴포넌트 사용 예, 함수형 컴포넌트를 사용하면 count까지 내려받을 필요가 없음
          setCount((prev) => prev + 1);
        }}
      >
        Count 1 증가
      </button>
    </div>
  );
};

export default Child;

리액트의 설계 이유

리액트는 성능 최적화를 위해 상태 업데이트를 단일 업데이트로 처리합니다. 이는 불필요한 리렌더링을 방지하고, 리소스 효율성을 높이기 위함입니다.

 

 

2. useEffect

정의

  • useEffect는 리액트 컴포넌트가 렌더링된 후 특정 작업을 수행할 수 있도록 하는 훅
  • 컴포넌트가 마운트되거나 업데이트될 때 실행

의존성 배열

  • useEffect는 의존성 배열을 통해 실행 시점을 제어할 수 있음
  • 빈 배열을 전달하면 처음 렌더링 시에만 실행되고, 특정 값을 배열에 넣으면 그 값이 변경될 때만 실행
  • 값이 입력됨에 따라 컴포넌트가 리렌더링되며 useEffect가 다시 호출되기 때문에, useEffect의 두 번째 인자인 의존성 배열로 이 문제를 해결할 수 있음 (의존성 배열: setEffect가 의존하고 있는 값의 배열 => 이 배열에 값을 넣으면 그 값이 바뀔 때만 useEffect를 실행 => 두 번째 인자에 배열을 넣고 어떤 값이 바뀔 때만 useEffect를 실행할 지 명시)
import React, { useEffect, useState } from "react";

const App = () => {
  const [value, setValue] = useState("");
  const [count, setCount] = useState(0);
  // 값을 입력함에 따라 useEffect가 호출: 컴포넌트가 리렌더링되며 useEffect가 다시 호출되기 때문에

  // useEffect(() => {
  //   // 최초에 한 번만 찍히길 원했지만 input을 입력할 때마다 찍히고 있음
  //   // => useEffect의 두 번째 인자인 의존성 배열로 해결 가능
  //   // 의존성 배열: useEffect가 의존하고 있는 값의 배열 => 이 배열에 값을 넣으면 그 값이 바뀔 때만 useEffect를 실행할게!
  //   console.log("Hello, useEffect.");
  // });

  // 두 번째 인자에 배열을 넣어줌: 그리고 어떤 값이 바뀔 때만 useEffect를 실행할지 명시
  // 현재 의존성 배열이 빈 배열 => trigger가 없음, 이런 경우 컴포넌트가 최초 렌더링이 됬을 때에만 이 로직이 수행이 됨,
  // 만약 배열에 어떤 값을 넣으면, 그 값을 참조하여 계속해서 수행됨
  useEffect(() => {
    console.log("Hello, useEffect.");
  }, []);

  return (
    <div>
      <h1>useEffect</h1>

      {/* 값이 입력됨에 따라 value에 해당되는 state가 변경되면서 리렌더링이 됨*/}
      {/* input에 값 입력 ->  value의 state가 변경 -> App 컴포넌트가 리렌더링 됨 -> useEffect() 다시 실행*/}
      <input
        type='text'
        value={value} //
        onChange={(e) => {
          setValue(e.target.value); //
        }}
      />
      {count}
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >증가</button>
    </div>
  );
};

export default App;

클린업 함수

  • useEffect는 컴포넌트가 언마운트될 때 클린업 함수를 실행할 수 있음
  • 이 클린업 함수는 컴포넌트가 사라질 때 실행되며, 주로 리소스 해제나 정리 작업에 사용

3. useRef

정의

  • useRef는 리액트에서 DOM 요소에 접근하거나, 상태 변화에 영향을 받지 않는 변수를 관리하는 데 사용
  • useRef로 생성된 객체는 컴포넌트가 리렌더링되더라도 값이 변하지 않음
  • useState처럼 저장공간의 역할을 하지만, useState 렌더링을 일으키고, useRef 선언한 값은 값을 바꾸어도 렌더링이 일어나지 않음

DOM 접근 및 유지

  • useRef를 사용하면 직접 DOM 요소에 접근할 수 있으며, 이 접근은 컴포넌트가 리렌더링되더라도 변하지 않음
  • 따라서 포커스 제어, 서드파티 라이브러리와의 통합 등에 주로 사용됨

저장 공간으로 사용

  • 상태 업데이트 없이 값을 유지해야 할 때도 useRef를 사용할 수 있음
  • 예를 들어, 이전 상태를 저장하거나 특정 값의 변화를 추적하는 데 유용
import React from "react";
import { useRef, useState, useEffect } from "react";

const App = () => {

  // ---------- (1) useRef vs useState ----------
  const [count, setCount] = useState(0);
  const countRef = useRef(0);

  // setCount를 이용해 count를 갱신
  const plusStateCountButtonHandler = () => {
    setCount(count + 1);
  };

  // countRef에 있는 current에 접근을 해서 ++
  const plusRefCountButtonHandler = () => {
    countRef.current++;
  };

  return (
    <>
      {/* ---------- (1) useRef vs useState ---------- */}
      <div>
        <h1>useRef vs useState</h1>
        <div>
          state영역입니다. {count}
          <br />
          <button onClick={plusStateCountButtonHandler}>state 증가</button>
        </div>
        <div>
          ref영역입니다. {countRef.current}
          <br />
          <button onClick={plusRefCountButtonHandler}>ref 증가</button>
        </div>
      </div>
    </>
  );
};

export default App;
import React from "react";
import { useRef, useState, useEffect } from "react";

const App = () => {
  // ---------- (2) 아이디 / 비밀번호 ----------
  const idRef = useRef("");
  // 최초 렌더링 시에만 아이디 포커싱
  useEffect(() => {
    idRef.current.focus();
  }, []);

  return (
    <>
      {/* ---------- (2) 아이디 / 비밀번호 ---------- */}
      <div>
        <div>
          아이디: <input type='text' ref={idRef} />
        </div>
        <div>
          비밀번호: <input type='password' />
        </div>
      </div>
    </>
  );
};

export default App;

4. useContext

정의

useContext는 리액트에서 컴포넌트 트리 전체에 걸쳐 전역적인 데이터를 공유할 수 있는 방법을 제공합니다. useContext를 사용하면 props를 통해 데이터를 전달할 필요 없이, 전역 상태에 직접 접근할 수 있습니다.

컨텍스트 생성 및 사용

컨텍스트는 React.createContext로 생성하며, 이를 Provider로 감싸서 하위 컴포넌트들이 접근할 수 있게 합니다. useContext를 사용하면 해당 컨텍스트의 값을 읽어올 수 있습니다.

컨텍스트와 상태 관리

상태 관리 라이브러리 없이도 useContext와 useReducer를 결합하여 복잡한 전역 상태 관리를 수행할 수 있습니다. 이는 작은 규모의 애플리케이션에서 유용합니다.