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 |