useRef
는 렌더링에 필요하지 않은 값을 참조할 수 있도록 도와주는 React Hook입니다.
DOM을 직접 조작하거나, 상태(state)와는 별개로 값을 저장할 때 유용하게 사용됩니다.
const ref = useRef(initialValue);
useRef
는 단일 프로퍼티인 current
를 가진 객체를 반환합니다.
useRef
와 useState
의 차이점useRef
: 값이 변경되어도 리렌더링이 발생하지 않음.useState
: 값이 변경되면 컴포넌트가 리렌더링됨.useRef
와 리렌더링import { useRef } from "react";
export default function AppRef() {
const countRef = useRef(0);
console.log("리렌더링!");
const handleClick = () => {
countRef.current++;
console.log(countRef.current); // 값이 업데이트되지만 리렌더링은 발생하지 않음
};
return (
<>
<h2>Count</h2>
<button onClick={handleClick}>Count</button>
</>
);
}
useState
와 리렌더링import { useRef, useState } from "react";
export default function AppRef() {
const [count, setCount] = useState(0);
console.log("리렌더링!");
const handleClick = () => {
setCount(count + 1); // 값이 업데이트되며 리렌더링 발생
console.log(count);
};
return (
<>
<h2>Count</h2>
<button onClick={handleClick}>Count</button>
</>
);
}
useRef
와 일반 변수(let)의 차이점useRef
: 컴포넌트가 리렌더링되어도 값이 초기화되지 않음.let
: 리렌더링될 때마다 값이 초기화됨.import { useRef, useState } from "react";
export default function AppRef() {
const countRef = useRef(0);
const [count, setCount] = useState(0);
let counter = 0;
console.log("리렌더링!");
const handleClick = () => {
counter++; // 리렌더링 시 값 초기화
countRef.current++; // 리렌더링 시 값 유지
setCount(count + 1); // 리렌더링 발생
console.log("counter:", counter, "countRef:", countRef.current);
};
return (
<>
<h2>Count</h2>
<button onClick={handleClick}>Count</button>
</>
);
}
1️⃣ 리렌더링 사이 정보 저장
2️⃣ 리렌더링 비발생
3️⃣ 로컬 저장
useRef
는 화면에 표시되는 정보를 저장하기에는 부적합합니다.
이런 경우에는 useState
를 사용하는 것이 더 적합합니다.
useRef
는 주로 DOM 조작에 사용됩니다.
다음 예제에서는 useRef
를 사용하여 DOM에 직접 접근하고 포커스를 관리합니다.
import { useRef, useState } from "react";
function ButtonCounter() {
const countRef = useRef(0);
const handleClick = () => {
countRef.current++; // 리렌더링 발생 없이 값 증가
console.log("CountRef:", countRef.current);
};
return <button onClick={handleClick}>Click Me</button>;
}
export default ButtonCounter;
countRef
의 값이 증가합니다.import { useRef, useState } from "react";
function Form() {
const [form, setForm] = useState({
title: "",
author: "",
content: "",
});
const titleInputRef = useRef(null);
const authorInputRef = useRef(null);
const contentTextareaRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
if (!form.title) {
titleInputRef.current.focus(); // 제목 입력란으로 포커스 이동
} else if (!form.author) {
authorInputRef.current.focus(); // 작성자 입력란으로 포커스 이동
} else if (!form.content) {
contentTextareaRef.current.focus(); // 내용 입력란으로 포커스 이동
} else {
console.log("폼 제출 성공!", form);
}
};
const handleChange = (e) => {
const { name, value } = e.target;
setForm((prev) => ({
...prev,
[name]: value,
}));
};
return (
<form onSubmit={handleSubmit}>
<input
ref={titleInputRef}
name="title"
placeholder="제목"
value={form.title}
onChange={handleChange}
/>
<input
ref={authorInputRef}
name="author"
placeholder="작성자"
value={form.author}
onChange={handleChange}
/>
<textarea
ref={contentTextareaRef}
name="content"
placeholder="내용"
value={form.content}
onChange={handleChange}
/>
<button type="submit">제출</button>
</form>
);
}
export default Form;
전체 앱을 구성하여 useRef
의 사용 사례를 다양하게 보여줍니다.
import ButtonCounter from "./ButtonCounter";
import Form from "./Form";
export default function AppRef() {
return (
<>
<h2>Button Counter</h2>
<ButtonCounter />
<h2>Form</h2>
<Form />
</>
);
}
이 앱은 카운터 버튼과 폼 입력 관리를 포함하며, useRef
의 다양한 활용을 체험할 수 있습니다.
useRef
는 기본적으로 DOM 접근과 상태 관리를 보조하는 데 사용됩니다. 이번 섹션에서는 useRef
를 고급스럽게 활용하는 방법과 주요 예제를 살펴봅니다.
import { useEffect, useRef, useState } from "react";
function MyForm() {
const [form, setForm] = useState({
title: "제목",
author: "명코딩",
content: "",
});
const titleInputRef = useRef(null);
const authorInputRef = useRef(null);
const contentTextareaRef = useRef(null);
const [isChanged, setIsChanged] = useState(false);
const prevForm = useRef(null);
useEffect(() => {
prevForm.current = { ...form }; // 이전 폼 데이터를 저장
}, []);
useEffect(() => {
// 이전 폼 데이터와 현재 폼 데이터를 비교해 변경 여부 확인
const hasChanged =
prevForm.current.title !== form.title ||
prevForm.current.author !== form.author ||
prevForm.current.content !== form.content;
setIsChanged(hasChanged);
}, [form]);
const handleForm = (e) => {
const { name, value } = e.target;
setForm({
...form,
[name]: value,
});
};
const handleSubmit = (e) => {
e.preventDefault();
if (!form.title) {
titleInputRef.current.focus();
return;
}
if (!form.author) {
authorInputRef.current.focus();
return;
}
if (!form.content) {
contentTextareaRef.current.focus();
return;
}
console.log("저장 성공!", form);
};
return (
<form onSubmit={handleSubmit}>
<fieldset>
<legend>글 쓰기</legend>
<input
ref={titleInputRef}
name="title"
placeholder="제목"
value={form.title}
onChange={handleForm}
/>
<hr />
<input
ref={authorInputRef}
name="author"
placeholder="작성자"
value={form.author}
onChange={handleForm}
/>
<hr />
<textarea
ref={contentTextareaRef}
name="content"
placeholder="내용"
value={form.content}
onChange={handleForm}
/>
<hr />
<button disabled={!isChanged}>전송</button>
</fieldset>
</form>
);
}
export default MyForm;
폼 데이터 관리
useState
를 활용하여 폼 데이터를 관리합니다.setForm
함수를 통해 입력값을 업데이트합니다.useRef
로 DOM 제어
titleInputRef
, authorInputRef
, contentTextareaRef
를 사용해 DOM 요소에 직접 접근합니다.폼 변경 감지
prevForm.current
에 이전 폼 데이터를 저장합니다.useEffect
를 통해 현재 폼 데이터와 이전 데이터를 비교하여 변경 여부를 확인합니다.제출 버튼 상태
isChanged
상태를 활용해 폼 데이터가 변경된 경우에만 "전송" 버튼이 활성화됩니다.기본적으로 React의 커스텀 컴포넌트는 내부 DOM 노드에 대한 ref를 외부로 노출하지 않습니다.
이를 해결하기 위해 forwardRef
를 사용하여 컴포넌트에 ref를 전달할 수 있습니다.
forwardRef
를 사용한 폼 컴포넌트import { forwardRef, useRef } from "react";
const MyForm = forwardRef((props, ref) => {
return (
<form ref={ref}>
<fieldset>
<legend>글 쓰기</legend>
<input placeholder="제목" />
<hr />
<input placeholder="작성자" />
<hr />
<textarea placeholder="내용" />
<hr />
<button>전송</button>
</fieldset>
</form>
);
});
export default MyForm;
import { useRef } from "react";
import MyForm from "./MyForm";
export default function AppRef() {
const formRef = useRef(null);
const handleFocus = () => {
console.log("Form Reference:", formRef.current);
};
return (
<>
<MyForm ref={formRef} />
<button onClick={handleFocus}>폼 참조 확인</button>
</>
);
}
forwardRef
로 ref 전달
forwardRef
를 사용하여 부모 컴포넌트에서 자식 컴포넌트의 DOM 참조를 사용할 수 있습니다.부모 컴포넌트에서 DOM 조작
formRef.current
를 통해 자식 컴포넌트의 DOM 요소에 접근 가능합니다.useRef
를 사용하여 다중 컴포넌트 간 상태를 동기화하거나 특정 값을 공유할 수 있습니다.