이전에 알아본 useState를 통해 사용자에게 입력을 받는 코드는 다음과 같이 나타낼 수 있다.
import { ChangeEvent, useState } from "react";
import PlayerInput from "./PlayerInput";
import PlayerButton from "./PlayerButton";
export default function Player() {
const [enteredPlayerName, setEnteredPlayerName] = useState("");
const [submitted, setSubmitted] = useState(false);
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
setSubmitted(false);
setEnteredPlayerName(e.target.value);
};
const handleClick = () => {
setSubmitted(true);
};
return (
<section className="text-center">
<h2 className="text-[#54a399]">Welcome {submitted ? enteredPlayerName : "unknown entity"}</h2>
<p className="flex justify-center items-center">
<PlayerInput type="text" value={enteredPlayerName} onChange={handleChange} />
<PlayerButton onClick={handleClick}>Set Name</PlayerButton>
</p>
</section>
);
}

위의 코드는 state를 통해 input의 value를 onChange이벤트가 발생할때마다 업데이트해주고, Set Name버튼을 클릭하면 unknown entity부분이 입력한 이름으로 바뀌게 하는 코드이다.
위의 코드는 잘 작동하지만 몇가지 문제점이 있다.
위의 두가지 문제점 중 2번을 중요하게 볼 필요가 있다.
리액트에서는 virtual dom이라는 것을 통해 이전의 dom에서 바뀐 부분만 실제로 repainting이 일어난다.
따라서 위의 코드에서 state에 의해 변경되는 h2태그와 input태그만 매 입력시마다 다시 그려진다.
하지만 repainting과 rerendering은 다른 개념으로, 그려지는 화면에 대한 최적화는 문제가 되지 않지만 컴포넌트가 리렌더링 되는 부분에서 최적화 문제가 발생할 수 있다.
만약 해당 리렌더링이 최상위 컴포넌트에서 일어난다면 그 컴포넌트에 종속된 하위 컴포넌트들도 모두 리렌더링이 일어나게 되고, 컴포넌트가 다시 실행될때마다 비용이 많이 드는 로직이 있을 경우 성능에 문제가 발생할 수 있다.
따라서 input을 이용할 때 불필요한 렌더링을 방지하기 위해 useRef를 사용해볼 수 있다.
useRef도 useState와 동일하게 컴포넌트 내부에서 렌더링이 일어나도 변경 가능한 상태값을 저장한다는 공통점이 있다.
하지만 다음과 같은 차이점이 존재한다.
useRef는 다음과 같이 선언하여 사용한다.
const inputRef = useRef(initialValue);
ref의 값에 접근하기 위해서는 current속성을 사용하면 된다.
해당 current의 값이 바뀌더라도 렌더링은 발생되지 않으며, 초기값은 initialValue와 같다.
ref로 정의한 변수를 특정 태그에 삽입하면 current는 해당 태그를 참조하는 상태가 된다.
위의 특징중 2번째 특징을 활용하면 불필요한 렌더링 없이 input의 값을 변경하여 사용할 수 있다.
import { useRef, useState } from "react";
import PlayerInput from "./PlayerInput";
import PlayerButton from "./PlayerButton";
export default function PlayerRef() {
const inputRef = useRef<HTMLInputElement>(null);
const [playerName, setPlayerName] = useState<string>();
const handleClick = () => {
setPlayerName(inputRef.current?.value);
if (inputRef.current) inputRef.current.value = "";
};
return (
<section className="text-center">
<h2 className="text-[#54a399]">Welcome {playerName ?? "unknown entity"}</h2>
<p className="flex items-center justify-center">
<PlayerInput type="text" ref={inputRef} />
<PlayerButton onClick={handleClick}>Set Name</PlayerButton>
</p>
</section>
);
}
위의 코드는 inputRef를 통해 <PlayerInput />에 ref객체를 참조시킨 후, 버튼이 클릭될 때 playerName의 상태를 해당 태그의 value로 업데이트 시켜주는 코드이다.
여기서 중요한 부분이 하나 있는데, 바로 inputRef.current.value = "";이 부분이다.
리액트는 보통 선언적 코드를 작성하는 반면 해당 부분에서는 명령적 코드를 통해 직접 DOM을 조작하고 있다.
따라서 해당 코드를 좋지 않은 코드라 생각할 수 있지만, 위의 상황에서는 input요소의 값을 React 상태로 관리하지 않고, 단순히 값을 가져오는 용도로만 사용하기 때문에 문제가 되지 않는다.