Frontend/Today I Learned

[React] Hooks - useState, useEffect, useRef

joycie416 2024. 8. 16. 20:29

오늘은 React의 몇 가지 hook에 관해 정리해보려고 한다.

 

1. `useState`

가장 기본적인 hook이며, 함수 컴포넌트에서 가변적인 상태를 갖게 해준다.

 

가장 기본적인 사용방법은 아래와 같다.

const [value, setValue] = useState(initialState);

// value를 nextState로 바꾸기
setValue(nextState);

그러면 초기에 `value`를 `initialState`로 설정하고 이후 `setValue`를 통해 `value`를 바꿔줄 수 있다. 하지만 이렇게 하면 setValue를 여러 번 수행한 경우, 마지막 setValue만 적용하게 된다. (렌더링 최적화를 위해 setState를 모아 한 번에 실행한다. batch update 방식) 

 

함수형 업데이트

특정한 값을 넣는 것 보다 내부에 함수를 사용해 값을 변경하면 여러번 수행해도 마지막 결과가 아닌 함수를 여러 번 수행한 결과값이 저장된다.

const [value, setValue] = useState(0);


// 1번
setValue(value + 1);
setValue(value + 1);
setValue(value + 1);
// 1번 결과 : 1


// 2번
setValue((prev) => prev + 1);
setValue((prev) => prev + 1);
setValue((prev) => prev + 1);
// 2번 결과 : 3


console.log(value);

1번을 수행하면 value가 3이 될 것 같지만 1일 것이다. 생각한대로 3이 되도록 하려면 2번을 수행해야한다.

이때 마지막에 setValue(value + 1)을 하게 되면 2번도 무시되고 1로 변경된다.

 

자식 컴포넌트에서 부모 컴포넌트의 변수 변경하기

함수형 업데이트를 알기 전에는 자식 컴포넌트에 value와 setValue를 모두 props로 전해주었을 것이다. 이제 함수형 컴포넌트로 setValue만 전달해서 value를 변경할 수 있다.

// src > App.jsx
import { useState } from 'react';

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 = ({setCount}) => {
  const handleAddCount = () => {
    setCount(prev => prev + 1);
  }

  return <button onClick={() => {handleAddCount}}>Count 1 증가</button>
}

export default Child;

 

 

 

 

2. `useEffect`

React 컴포넌트가 렌더링 된 이후마다 특정 작업을 수행하도록 설정하는 hook.

 

기초적인 사용법은 아래와 같다.

// src/App.js

import React, { useEffect } from "react";

const App = () => {

  useEffect(() => {
    //처음 시작 직후 한번 출력됨
    console.log("hello useEffect");
  });

  return <div>Home</div>;
}

export default App;

 

리렌더링은 state가 변경될 때마다 이루어진다. 즉 useState통해 state를 변경시킬 때 마다 "hello useEffect"가 출력 될 것이다. 즉. react + vite 기본 코드에 위 코드를 추가하면 count 버튼을 누를 때마다 "hello useEffect"가 출력된다.

 

의존성 배열

그런데 여러 state 중 하나만 변경되어도 실행된다면 너무 비효율적일 것이다. 이를 해결하기 위해 '의존성 배열(dependency array)'이 존재한다. 해당 배열에 있는 값이 바뀔 때만 실행된다.

function App() {
  const [value, setValue] = useState('')
  const [count, setCount] = useState(0)

  useEffect(() => {
    console.log('hello useEffect')
  },[value])

  return (
    <>
      <h1>useEffect</h1>
      <input
      type='text'
      value={value}
      onChange={(e) => {
        setValue(e.target.value);
      }}>

      </input>
      <button
      onClick={() => {setCount(count+1)}}>{count}</button>
    </>
  )
}

위와 같은 코드를 실행해보면 `useEffect`가 `value`에만 의존하도록 설정되어있다. 즉 input에 값을 입력할 때마다 "hello useEffect"가 출력되지만 버튼을 눌렀을 땐 출력되지 않는다.

 

 

3. `useRef`

렌더링과 상관없이 특정 값을 저장할 수 있다.

 

기본적인 사용방법은 다음과 같다.

const ref = useRef(initialValue);

console.log(ref) // {current : initialValue}

값을 변경하려면 객체에 값을 변경하듯 변경하면 된다.

ref.current = nextValue;

console.log(ref) // {current : nextValue}

 

이렇게 보면 `useState`와의 차이점을 잘 모를 수 있다. `useState`는 `setState`를 할 때마다 리렌더링되지만, `useRef`는 리렌더링되지 않는다. 정리하면 다음과 같다.

`useState`는 값이 변경될 때 리렌더링이 필요한 경우 사용하고, `useRef`는 값이 변경되지만 리렌더링은 필요없는 경우 사용하면 된다.

 

이전 React 포스팅에서 다룬 프로젝트에서 input 태그에 `onChange`와 `setState`를 같이 사용하였다. 하지만 입력될 때마다 state가 변경되면 무의미한 리렌더링이 무수히 많이 일어날 것이다. 이럴 때 `useRef`를 사용하면 리렌더링이 일어나지 않고 값을 저장할 수 있다.

 

아래 코드를 실행해보면 'ref 증가' 버튼은 눌러도 변화가 없다가 'state 증가' 버튼을 누르면 변화가 생긴다. 즉 `setCount`에 의해 state 변화가 생겨 리렌더링이 발생해 변경된 값이 적용된 것이다.

function App() {
  const [count, setCount] = useState(0);
  const countRef = useRef(0);

  const plusStateCountButtonHandler = () => {
    setCount(count + 1)
  }

  const plusRefCountButtonHandler = () => {
    countRef.current++;
    // 누른다고 바로 화면에 변화가 보이진 않음
    // 리렌더링되면(=Count 버튼을 누르면) 변화한 값이 보임
  }

  return (
    <>
    <h1>useRef & useState</h1>
    <div>
      state 영역. {count} <br></br>
      <button onClick={plusStateCountButtonHandler}>state 증가</button>
    </div>
    <div>
      Ref 영역. {countRef.current} <br></br>
      <button onClick={plusRefCountButtonHandler}>ref 증가</button>
    </div>
    </>
  )
}

 

useRef를 이용해서 DOM 요소에도 접근할 수 있다.

function App() {
  const idRef = useRef('');
  // 최초 렌더링 시에만 ref={idRef} 가 잇는 부분에 focus
  useEffect(() => {
    idRef.current.focus();
  }, [])

  return (
    <>
    <h1>useRef & useState</h1>
    <div>
      <div>
        아이디 : <input type='text' ref={idRef}/>
      </div>
      <div>
        비밀번호 : <input type='password'/>
      </div>
    </div>
    </>
  )
}

리렌더링 후 id input칸으로 focus를 할 수 있다.

 


다음에는 `useContext`와 `useMemo`에 대해서 정리할 계획이다.