Hook series 3 : useRef와 비제어 컴포넌트🫵

밍갱·2025년 2월 7일

REACT

목록 보기
7/9

1. useRef📍

01. useRef란?

useRef는 저장공간 또는 특정 DOM 요소에 접근하기 위해 사용하는 Hook이다. (Ref 는 reference 참조를 뜻한다.) JavaScript에서 특정 DOM을 조작하기 위해 querySelector나 getElementBy 등을 사용한다. React에서는 useRef를 사용한다. 또한, useRef를 사용하면 불필요한 리렌더링을 방지할 수 있다.

02. useRef 사용법

컴포넌트의 최상위 레벨에서 useRef를 호출하여 사용한다.

  • 매개변수 : 초기값
  • 반환값 : {current: 초기값} 객체가 반환
  • 요소에 접근 : ref={변수명}으로 접근
const 변수명 = useRef(초기값)
<타겟 태그 ref={변수명} />

//예시
import { useRef, useState } from 'react';

const App = () => {
	//useRef 호출
	const inputRef = useRef(null)
    const [todos, setTodos] = useState('')
    
    const handleSubmit = (e) => {
    	...생략
        //useRef로 참조한 값으로 state 변경
        const newTodo = inputRef.current.value.trim();
    	if (newTodo) {
      		setTodos([...todos, newTodo]);
      	}
        ...생략
        
    return(
    	<form onSubmit={handleSubmit}>
        	//참조할 요소 = input
    		<input type="text" ref={inputRef} />
        ...이하 생략
    )

2. 제어 컴포넌트와 비제어 컴포넌트🪆

01. 제어 컴포넌트 / 비제어 컴포넌트란?

  • 제어 컴포넌트
    React 컴포넌트에서는 state 속성을 유지하고, 사용자의 입력과 setState에 의해 업데이트 된다. 즉, React에 의해 값이 제어되기 때문에 제어 컴포넌트 라고 한다.
  • 비제어 컴포넌트
    비제어 컴포넌트 는 바닐라 JavaScript와 크게 다르지 않은 방식으로 state에 접근한다. 요소 내부의 값(e.target.value)을 직접적으로 가져오는 방식으로, setState가 아닌 ref를 사용한다.

02. 제어 컴포넌트의 단점

간단한 todoList 예제로 단점을 살펴보자.

import { useState } from 'react';

function ControlledTodoList() {
  const [inputValue, setInputValue] = useState(''); // 입력 필드 상태 관리
  const [todos, setTodos] = useState([]); // 할 일 목록 상태 관리

  const handleInputChange = (e) => {
    setInputValue(e.target.value);
  };

  const handleAddTodo = (e) => {
    e.preventDefault();
    
    if (inputValue.trim()) {
      setTodos([...todos, inputValue.trim()]);
      setInputValue('');
    }
  };

  return (
    <div>
      <h2>Todo List</h2>
      <form onSubmit={handleAddTodo}>
        <input
          type="text"
          value={inputValue}
          onChange={handleInputChange}
          placeholder="Enter a task"
        />
        <button type="submit">Add</button>
      </form>
    </div>
  );
}

export default ControlledTodoList;

만약 <input> 태그에 "안녕하세요"를 입력한다고 가정해보자.

ㅇ 
아 
안 
안ㄴ 
안녀 
안녕 
안녕ㅎ 
안녕하 
안녕핫 
안녕하 
안녕하세 
안녕하셍 
안녕하세 
안녕하세요 

이렇게 불필요한 값도 갱신된다. 제어 컴포넌트의 값은 항상 최신 상태를 유지하며, UI에서 입력한 값과 저장된 데이터가 동기화되기 때문이다. 이로 인해 불필요한 리렌더링과 API 요청이 발생할 수 있어 자원이 낭비될 수도 있다.

03. useRef와 리렌더링

위에서 useRef로 리팩토링한 todoList 예제로 제어/비제어 컴포넌트의 차이점을 살펴보자.

import { useRef, useState } from "react";

function TodoListWithUseRef() {
  // state 대신 참조 변수(ref) 사용
  const inputRef = useRef(null); // inputRef = input을 참조하는 변수
  const [todos, setTodos] = useState([]); // 할 일 목록 상태 관리

  const handleAddTodo = (e) => {
    e.preventDefault();

	// useRef는 {current: 값} 객체를 반환
    // 즉, inputRef.current로 input 요소를 직접 참조할 수 있다.
    const newTodo = inputRef.current.value.trim();
    
    if (newTodo) {
      setTodos([...todos, newTodo]);
      inputRef.current.value = "";
    }
  };

  return (
    <div>
      <h2>Todo List</h2>
      <form onSubmit={handleAddTodo}>
        <input type="text" ref={inputRef} placeholder="Enter a task" />
        <button type="submit">Add</button>
      </form>
    </div>
  );
}

export default TodoListWithUseRef;

똑같이 <input> 태그에 "안녕하세요"를 입력해보자.

안녕하세요

비제어 컴포넌트는 UI에 입력한 값과 데이터 상태가 동기화 되지 않고, UI에서 값을 트리거(onSubmit) 해야 값을 얻을 수 있다.

04. 제어 컴포넌트 vs 비제어 컴포넌트 정리

  • 제어 컴포넌트 사용
    즉각적인 값에 대한 검증(유효성 검사, 입력 형식 적용 등)이 필요할 때
    특정 조건이 충족될 때만 제출 버튼을 활성화해야 할 때
    입력값을 실시간으로 UI에 반영해야 할 때

  • 비제어 컴포넌트 사용
    즉각적인 피드백이 불필요할 때
    제출시에만 값이 필요할 때
    불필요한 렌더링과 값 동기화 불필요할 때

05. FormData를 이용한 비제어 컴포넌트

React Hook useRef가 아닌 JavaScript 메서드인 FormData를 사용하여 비제어 컴포넌트를 만들 수 있다. FormData는 비제어 컴포넌트뿐만 아니라 제어 컴포넌트에서도 사용할 수 있고, input 값이 상태에 직접 연결되지 않아 렌더링이 최소화된다. 하지만 useRef와 달리, FormData는 단순히 값을 가져오는 용도이므로 폼이 복잡해질 경우 useRef가 더 적합할 수 있다.

  • 사용방법
    FormData는 Hook이 아닌 JavaScript 문법이기에 FormData 관련 MDN 문서를 참고하면 된다. 인스턴스 메서드도 많으니 꼭 참고하자!

위에서 useRef를 사용한 todoList 예제를 FormData로 리팩토링해보자.

import React, { useState } from 'react';

function TodoListWithFormData() {
  const [todos, setTodos] = useState([]);

  const handleAddTodo = (e) => {
    e.preventDefault();
    
    //FormData 사용
    const formData = new FormData(e.target);
    const newTodo = formData.get('todo').trim(); // 'todo' 필드 값 가져오기

    if (newTodo) {
      setTodos([...todos, newTodo]); // 새로운 항목 추가
      e.target.reset(); // 입력 필드 초기화
    }
  };

  return (
    <div>
      <h2>Todo List</h2>
      <form onSubmit={handleAddTodo}>
        <input type="text" name="todo" placeholder="Enter a task" />
        <button type="submit">Add</button>
      </form>
    </div>
  );
}

export default TodoListWithFormData;

useRef 공식 문서
useRef 참고 사이트
제어 컴포넌트/비제어 컴포넌트 참고 사이트

profile
미술 전공에서 프론트엔드 개발까지

0개의 댓글