프론트엔드/실전 리액트 프로그래밍

[스터디 with 실전 리액트 프로그래밍] 13편 - useRef

Junheehee 2022. 8. 16. 16:04

실전 리액트 프로그래밍

 

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이다.

 

 

Special Props Warning – React

A JavaScript library for building user interfaces

reactjs.org

 

따라서 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>
  );
}

 

 

 

useRef 사용

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>
  );
}

useRef 주의할점

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>
  );
}

useRef로 값 저장

버튼을 클릭하면 어떤 일이 발생하는지 살펴보자.

  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의 타이머를 관리할 때도 많이 사용된다.

 

 

 

 

코드

 

 

GitHub - junhee-won/react-study: with 실전 리액트 프로그래밍

with 실전 리액트 프로그래밍. Contribute to junhee-won/react-study development by creating an account on GitHub.

github.com