ref
리액트로 개발하다 보면 돔 요소에 직접 접근해야 할 떄가 있다.
이때 ref 속성을 사용하면 자식 요소(컴프넌트, 돔 요소)에 직집 접근 할 수 있다.
import React, { useEffect, useRef } from "react";
export default function TestUseRef() {
const inputRef = useRef();
useEffect(() => {
console.log(inputRef);
inputRef.current.focus();
}, []);
return (
<div>
<input type="text" ref={inputRef} /> <button>save</button>
</div>
);
}
useRef 훅이 반환하는 ref 객체를 이용하면 자식 요소에 접근할 수 있다.
접근하고자 하는 요소의 ref 속성에 ref 객체를 넣으면 된다.
props로 ref 전달하기
리액트에서 컴포넌트에 props로 사용할 수 없는 것들이 있는데, ref와 key이다.
따라서 ref를 컴포넌트에 전달하고 싶다면, 속성명을 달리 해야 한다.
import React, { useEffect, useRef } from "react";
export default function TestUseRef() {
const inputRef = useRef();
useEffect(() => {
console.log(inputRef);
inputRef.current.focus();
}, []);
return (
<div>
<Input inputRef={inputRef} />
<button onClick={() => inputRef.current.focus()}>입력창으로 이동</button>
</div>
);
}
function Input({ inputRef }) {
return (
<div>
<input type="text" ref={inputRef} /> <button>save</button>
</div>
);
}
Input 컴포넌트에 ref를 전달하기 위해 inputRef로 이름을 바꿔주었다.
TestUseRef 컴포넌트에서 '입력창으로 이동' 버튼을 누르면 자식 요소인 input 태그가 focus 된다.
이 방법은 자식 컴포넌트에 내부 구조를 알아야 하므로 좋은 방법은 아니다.
forwardRef
forwardRef는 컴포넌트에 ref 객체를 props로 전달할 수 있는 또하나의 방법이다.
위 방법과 다른점은 ref 속성명을 그대로 사용해도 된다는 점이다.
import React, { useEffect, useRef } from "react";
export default function TestForwardRef() {
const inputRef = useRef();
useEffect(() => {
console.log(inputRef);
inputRef.current.focus();
}, []);
return (
<div>
<Input ref={inputRef} />
<button
onClick={() => inputRef.current.focus()}
>
입력창으로 이동
</button>
</div>
);
}
const Input = React.forwardRef((props, ref) => {
return (
<div>
<input type="text" ref={ref} /> <button>save</button>
</div>
);
});
부모 컴포넌트에서 ref 객체를 ref 속성에 그대로 전달했다.
자식 컴포넌트는 forwardRef 이용해 넘어온 ref 속성값을 직접 처리할 수 있다.
current가 null인 경우
useRef를 사용할 때 ref 객체의 current 속성이 없는 경우도 있다.
import React, { useState, useRef } from "react";
export default function TestUseRef2() {
const inputRef = useRef();
const [showText, setShowText] = useState(true);
return (
<div>
{showText && <input type="text" ref={inputRef} />}
<button onClick={() => setShowText(!showText)}>
텍스트 보이기/가리기
</button>
<button
onClick={() => inputRef.current.focus()}
>
텍스트 이동
</button>
</div>
);
}
showText가 false라면 input 요소가 존재하지 않고 이럴 경우 ref의 current 속성이 존재하지 않는다.
이를 해결하기 위해선, current가 존재하는지 체크해주면 된다.
<button
onClick={() => inputRef.current && inputRef.current.focus()
>
렌더링과 무관한 값 저장하기
useRef는 자식 요소에 접근하는 것 외에도 중요한 용도가 한 가지 더 있다.
컴포넌트 내부에서 생성되는 값 중 렌더링과 무관하게 저장해야 하는 경우에도 useRef를 사용한다.
useRef를 이용하여 렌더링 전의 값과 렌더링 후의 값을 비교해보자.
import React, { useState, ueRef, useEffect, useRef } from "react";
export default function Profile() {
const [age, setAge] = useState(25);
const prevAgeRef = useRef(25);
useEffect(() => {
prevAgeRef.current = age;
}, [age]);
const prevAge = prevAgeRef.current;
const text = age === prevAge ? "same" : age > prevAge ? "older" : "younger";
return (
<div>
<p>{`age ${age} is ${text} than prevAge ${prevAge}`}</p>
<button
onClick={() => {
const age = Math.floor(Math.random() * 50 + 1);
setAge(age);
}}
>
나이변경
</button>
</div>
);
}
버튼을 클릭하면 어떤 일이 발생하는지 살펴보자.
age | prevAge | prevAgeRef | text | |
최초 렌더링 |
25 | 25 | { current: 25} | same |
버튼 클릭 |
47 | 25 | { current: 25} | same |
setAge 호출 -> 렌더링 시작 |
47 | 25 | { current: 25} | older |
렌더링 끝 -> useEffect 호출 |
47 | 25 | { current: 47 } | older |
만약 여기서 버튼을 한번 더 눌렀을 때, 렌더링 과정 중 5번째 줄에 의해 prevAgeRef는 25가 될까?
아니다, useRef로 반환한 값이기 때문에 렌더링이 되었다고 해서 값이 바뀌지 않는다.
useRef를 이용하면 이렇게 렌더링에 무관하게 값을 관리할 수 있다.
setTimeout의 타이머를 관리할 때도 많이 사용된다.
코드
'프론트엔드 > 실전 리액트 프로그래밍' 카테고리의 다른 글
[스터디 with 실전 리액트 프로그래밍] 15편 - 리덕스 (0) | 2022.08.21 |
---|---|
[스터디 with 실전 리액트 프로그래밍] 14편 - 여러가지 훅(Hook) (0) | 2022.08.16 |
[스터디 with 실전 리액트 프로그래밍] 12편 - Context API (0) | 2022.08.16 |
[스터디 with 실전 리액트 프로그래밍] 11편 - 훅(Hook) (0) | 2022.08.16 |
[스터디 with 실전 리액트 프로그래밍] 10편 - 가상돔 (0) | 2022.08.15 |