useRef
기존 바닐라 자바스크립트에서는 input element가 필요하면 getElementByID
, querySelector
등을 이용해 해당 DOM을 가져와서 사용한다.
리액트에서는 useRef
라는 것을 사용한다. 여러 HTML 엘리먼트 중에서도 <input>
을 제어할 때 많이 사용된다. usestate
는 값이 변경되었을 때, 함수 컴포넌트를 리렌더링하지만, useRef
는 리렌더링이 발생하지 않는다.
useRef
의 일반적인 사용법ref
를 넘겨주면, 해당 DOM element를 current
에 담아준다.input.current.value
, input.current.focus()
등null
을 넣는다.const input = useRef(null);
useRef
?ref
는 DOM을 담을 때만 사용하나? (❌)ref
는 값이 바뀌어도 컴포넌트가 리렌더링 되지 않는다? (⭕)focus()
등)input
이 렌더되기 전까지 input.current
는 null
이므로 if문으로 유효한지 체크해야 한다.
// useRef를 사용하여, Click to Reset 버튼을 클릭하면
// input의 value를 초기화하도록 만들기
import React, {useRef} from 'react'
export default function Practice1() {
const input = useRef(null);
const handleClick = () => {
// 화면에 그려지기 전까지는 input.current가 null이다.
// input.current가 유효한지 체크하는 것!
if (input.current) {
console.log(input.current);
console.log(input.current.value);
input.current.value = null;
input.current.disabled = true;
}
}
return (
<div>
<input type="text" ref={input} />
<button type="button" onClick={handleClick}>Click to Reset</button>
</div>
)
}
import React, {useRef, useState} from 'react'
export default function Practice2() {
const input = useRef(null);
const [value, setValue] = useState("");
const handleClick = () => {
if (input.current) {
input.current.value = null;
setValue(null);
}
}
const handleChangeInput = (event) => {
setValue(event.target.value);
}
return (
<div>
<div>현재 value는 {value}입니다.</div>
<input type="text" ref={input} onChange={handleChangeInput} />
<button type="button" onClick={handleClick}>Click to Reset</button>
</div>
)
}
🤔 만일,
setValue('')
이 누락되면?
렌더가 일어나지 않으므로 Reset 버튼을 눌러도 text 부분의 value 값이 그대로 남아있을 것이다.
🤔 반대로,
input.current.value = ''
이 누락되면?
리액트 state에 있는 value만 업데이트 해준 것이므로 input의 value 값이 그대로 남아있을 것이다.
이처럼 둘 중에 하나라도 업데이트 하지 않으면 Bug가 발생한다. 👉 불편하다!
🤔 그럼 그냥
getElementByID
,querySelector
사용하면 안 되나?
getElementByID
,querySelector
등은 DOM 트리를 돌면서 element 하나하나 검사해야 한다. 브라우저 입장에서 메모리를 소모하는 것이다. (부하 발생)
Ref
로 변수에 대입만 해주는 것은 위와 같은 과정을 생략할 수 있는 것!
하나의 state를 이용해서 여러 가지를 컨트롤 하는 것이다.
02-1(Uncontrolled)을 다음처럼 prop으로 value
를 넘겨주어서 Controlled로 바꿀 수 있다. (useState
를 통해 업데이트 된 value
를 input
의 value
로 넘겨주는 것!)
그럼 useRef
는 필요 없는 거 아니야?
❌ 아니다.
Controlled와 Uncontrolled는 상호배타적이지만, ref
는 필요하다면 어디서는 사용할 수 있다.
또한, disabled
과 같은 경우 true
/false
를 prop으로 넘겨서 제어할 수 있다. (Ref
를 사용하지 않아도 됨)
하지만, focus()
를 해야 하는 경우엔 prop으로 제어할 수 없어서 Ref
를 사용할 수밖에 없다.
// useRef를 사용하여, Click to Reset 버튼을 클릭하면
// input의 value를 초기화하도록 만들기
// input 위에 현재 value를 알려주는 문구 추가하기
import React, {useState} from 'react'
export default function Practice3() {
const input = React.useRef(null);
const [value, setValue] = useState("");
const handleClick = (props) => {
setValue('');
if(input.current) {
input.current.focus();
}
}
const handleChangeInput = (event) => {
setValue(event.target.value);
}
return (
<div>
<div>현재 value는 {value}입니다.</div>
<input type="text" ref={input} value={value} onChange={handleChangeInput} />
<button type="button" onClick={handleClick}>Click to Reset and Focus!</button>
</div>
)
}
useState
, useRef
를 이용해 회원가입 Form 유효성 검사 구현하기
- id는 6글자 이상 20글자 이하인 경우 유효
- password는 12글자 이상 20글자 이하인 경우 유효
- 유효하지 않는 input 밑에 "유효하지 않은 ~~입니다." 출력
👉 값 입력 시 처리되어야 하므로onChange
에handleChangeInput
컴포넌트를 주어 이 컴포넌트에서 처리하였다.
- 각
input
에 값이 입력될 때,useState
로 입력된 값을 각각id
,password
에 저장한다.- 삼항연산자 중첩. 먼저,
id
에 값이 있다면 유효한 조건에 맞는지 체크하고, 값이 없을 때는 메시지가 출력되지 않도록 했다.(null
)id
에 값이 있을 때, 유효한 조건에 맞는지 체크하는데, 조건에 맞다면 메시지를 출력하지 않고(null
) 조건에 맞지 않다면 메시지를 출력한다.(idMsg
)
- id와 password가 둘 다 비어있으면 회원가입 버튼 disable 처리
👉 삼항연산자를 사용하여 id와 password 모두 값이 있을 때만disabled
가false
가 되도록 한다. (disabled={(id || password) ? false : true}
)
- 유효하지 않은 input이 존재하는 경우 회원가입 버튼 클릭 시 에러 alert를 띄워주고, 해당 input reset하고, focus 시켜주기
👉 버튼을 클릭했을 때, 발생해야 하므로handleClick
에서 처리하였다.
- 유효 조건에 맞지 않다면
idMsg
를alert
으로 띄워주었다.- 그리고
useRef
를 통해 해당input
을focus
했다. (idRef.current.focus();
)- 이때,
input
에 입력한 값을 초기화하기 위해 setter 함수를 이용했다. (setId('');
,<input value={id} />
)
idRef.current.value='';
를 사용하면input
에 입력된 값은 사라지지만, 옆에 문구(idMsg
)가 사라지지 않는다!
import React, {useState, useRef} from 'react'
export default function Practice4() {
const [id, setId] = useState("");
const [password, setPassword] = useState("");
const idRef = useRef(null);
const passwordRef = useRef(null);
const idMsg = "유효하지 않은 id입니다.";
const passwordMsg = "유효하지 않은 password입니다.";
const idCheck = (id.length >= 6 && id.length <= 20);
const passwordCheck = (password.length >= 12 && password.length <= 20);
const handleChangeInput = (e) => {
if(e.target.name === "id") {
setId(e.target.value);
}
if(e.target.name === "password") {
setPassword(e.target.value);
}
}
const handleClick = () => {
if ( !idCheck ) {
alert(idMsg);
setId('');
idRef.current.focus();
}
else if ( !passwordCheck ) {
alert(passwordMsg);
setPassword('');
passwordRef.current.focus();
}
else alert('회원가입 성공!');
};
return (
<div>
<div>
<input type="text" name='id' ref={idRef} value={id} onChange={handleChangeInput} placeholder='6글자 이상 20글자 이하' />
{id ?
idCheck ? null : idMsg
: null}
</div>
<div>
<input type="text" name='password' ref={passwordRef} value={password} onChange={handleChangeInput} placeholder='12글자 이상 20글자 이하' />
{password ?
passwordCheck ? null : passwordMsg
: null}
</div>
<button type="button" onClick={handleClick} disabled={
(id || password) ? false : true
}>회원가입</button>
</div>
);
}