Javascript 를 사용할때, 특정 DOM 을 선택하여 정보를 얻거나 임의로 조작해야 할때, getElementById 혹은 querySelector 과 같은 DOM Selector 함수를 사용하여 DOM 을 선택하였다. 하지만, React 는 이 기능을 대체할 수 있는 useRef 훅을 제공한다.
ref를 넘겨주면, 해당 dom element 를 current에 담아준다.
Ref의 바람직한 사용 사례
Ref 특징
아래의 예제들을 통해 controlled/uncontrolled input 의 개념을 정리하고, 적절한 ref 사용 상황을 살펴보자
첫 예제는 아래 사진과 같이 input 을 입력하면 그 값을 받아서 바로 ui 로 적용하고, 리셋버튼을 누르면 리셋되는 아주 간단한 상황이다.
import { useRef, useState } from "react";
const UseRefEx = () => {
const input = useRef(null);
const [value, setValue] = useState(0);
const handleClick = () => {
if (input.current) {
input.current.value = "";
setValue("");
}
};
return (
<div>
<p>현재 value는 {value} 입니다.</p>
<input
type="text"
ref={input}
onChange={() => {
setValue(input.current.value);
}}
/>
<button type="button" onClick={handleClick}>
Click to Reset
</button>
</div>
);
};
export default UseRefEx;
이 예제는 실시간으로 바뀌는 input value값에 대한 처리가 불편하다.
const handleClick = () => {
if (input.current) {
input.current.value = "";
setValue("");
}
};
이와 같이 ref의 input과 state의 value 를 따로 바꿔줘야한다.
실제로 값을 따로 관리하고 있기 때문이다. 이러한 것을 uncontrolled input 이라한다.
const UseRefEx = () => {
const [value, setValue] = useState(0);
const handleClick = () => {
setValue("")
};
return (
<div>
<p>현재 value는 {value} 입니다.</p>
<input
type="text"
value={value}
onChange={(e) => {
setValue(e.target.value);
}}
/>
<button type="button" onClick={handleClick}>
Click to Reset
</button>
</div>
);
};
export default UseRefEx;
react state로써 들고있는 value를 input 에 push하여 sync
input 의 value 에 value 를 대응시켜주면, 값을 한번에 컨트롤 할 수 있는 controlled input 형태이다.
그렇다면, useRef 와 uncontrolled input은 필요없고 controlled input만 알고있으면 될까?
그렇지 않다!! ref 를 사용하기 바람직한 대표적인 경우가 input에 focus 주는 경우이다.
import { useRef, useState } from "react";
const UseRefEx = () => {
const input = useRef(null);
const [value, setValue] = useState("");
const handleClick = () => {
setValue("");
input.current.focus(); // 포커스
};
return (
<div>
<p>현재 value는 {value} 입니다.</p>
<input
type="text"
value={value}
ref={input}
onChange={(e) => {
setValue(e.target.value);
}}
/>
<button type="button" onClick={handleClick}>
Click to Reset
</button>
</div>
);
};
export default UseRefEx;
이처럼 controlled input과 uncontrolled input은 상호배타적이지만
(필요하다면) ref는 양 쪽 어디서든 사용할 수 있다.
위에서 배운 내용을 토대로 실무에서 사용할 법한 input (id, password, email) 유효성 검사를 구현해보았다.
조건
import { useRef, useState } from "react";
const UseRefEx = () => {
// 포커스를 주기 위한 useRef
const inutRef = useRef([]); // ref 배열형태로 저장해서 여러 값을 인덱스로 컨트롤 가능
// input value state 관리
const [inputs, setInputs] = useState({
id: "",
password: "",
email: "",
});
const { id, password, email } = inputs; // 구조분해할당
//유효한 id, password, email 조건 변수에 담아 사용
const vaildId = id.length >= 6 && id.length <= 20;
const vaildPassword = password.length >= 12 && password.length <= 20;
const regexp = /^[0-9a-zA-Z]+@[0-9a-zA-Z]+\.[0-9a-zA-Z]/; // email 형식 정규표현식
const vaildEmail = email.match(regexp);
// onChange 함수로 state 값 바꿔주기
const handleChange = (e) => {
setInputs({
...inputs,
[e.target.name]: e.target.value,
});
};
// 클릭이벤트 : 유효성에 맞는 이벤트 이루어지도록
const handleClick = () => {
if (!vaildId) {
alert("유효하지 않은 id 입니다."); // 알람창
setInputs({ // 값 비워주기
...inputs,
id: "", // 바뀐 값 빼고 나머지는 그대로 스프레드 연산자
});
inutRef.current[0].focus(); // 자동 포커스
} else if (!vaildPassword) {
alert("유효하지 않은 password 입니다.");
inutRef.current[1].focus();
setInputs({
...inputs,
password: "",
});
} else if (!vaildEmail) {
alert("유효하지 않은 email 입니다.");
inutRef.current[2].focus();
setInputs({
...inputs,
email: "",
});
} else {
return alert("회원가입 성공!");
}
};
return (
<div>
<div>
<input
type="text"
name="id"
placeholder="6글자 이상 20글자 이하"
value={id}
onChange={handleChange}
ref={(el) => (inutRef.current[0] = el)}
/>
{vaildId ? null : <span>유효하지 않은 id 입니다.</span>}
</div>
<div>
<input
type="text"
name="password"
placeholder="12글자 이상 20글자 이하"
value={password}
onChange={handleChange}
ref={(el) => (inutRef.current[1] = el)}
/>
{vaildPassword ? null : <span>유효하지 않은 password 입니다.</span>}
</div>
<div>
<input
type="text"
name="email"
placeholder="유효한 이메일 작성"
value={email}
onChange={handleChange}
ref={(el) => (inutRef.current[2] = el)}
/>
{vaildEmail ? null : <span>유효하지 않은 e-mail입니다.</span>}
</div>
<button
type="button"
onClick={handleClick}
disabled={id.length < 1 && password.length < 1 && email.length < 1}
>
회원가입
</button>
</div>
);
};
export default UseRefEx;