React hook의 모든 것(1) (useRef)

신은수·2023년 5월 21일
0

ReactJS

목록 보기
3/13

전에는 useState, useEffect로 거의 99프로의 코딩을 했었지만, 이제는 조금 달라져야하지 않을까 하여 useRef, useMemo, useLayoutEffect 등의 훅들을 정리하는 포스트를 작성하게 되었다.

1) useRef

useRef 함수는 current 속성을 가지고 있는 객체를 반환하는데, 인자로 넘어온 초기값을 current 속성에 할당한다. 이 current 속성은 값을 변경해도 상태를 변경할 때처럼 React 컴포넌트가 다시 랜더링되지 않는다.

  const num = useRef(0);

  <button onClick={() => {
            num3.current += 1;
            console.log(num.current);
          }}>
  </button>


2) useRef를 사용한 Counter 예제

a) useRef 사용전 문제점

import React, { useState } from "react";

function ManualCounter() {
  const [count, setCount] = useState(0);

  let intervalId;

  const startCounter = () => {
    intervalId = setInterval(() => setCount((count) => count + 1), 1000);
  };
  const stopCounter = () => {
    clearInterval(intervalId);
  };

  return (
    <>
      <p>자동 카운트: {count}</p>
      <button onClick={startCounter}>시작</button>
      <button onClick={stopCounter}>정지</button>
    </>
  );
}

export default ManualCounter;

-> 이 코드의 문제점은 안에서 선언된 intervalId 변수를 startCounter() 함수와 stopCounter() 함수가 공유할 수 있도록 해줘야 한다는 것이다. 그럴려면 intervalId 변수를 두 함수 밖에서 선언해야하는데 그럴 경우, count 상태값이 바뀔 때 마다 리렌더링 되어 intervalId도 매번 새로 선언된다. 따라서, 브라우저 메모리에는 미처 정리되지 못한 intervalId 들이 1초에 하나씩 쌓여나갈 것이다.

b) useRef 쓰면?

import React, { useState, useRef } from "react";

function ManualCounter() {
  const [count, setCount] = useState(0);
  const intervalId = useRef(null);

  const startCounter = () => {
    intervalId.current = setInterval(
      () => setCount((count) => count + 1),
      1000
    );
    console.log(`시작... intervalId: ${intervalId.current}`);
  };

  const stopCounter = () => {
    clearInterval(intervalId.current);
    console.log(`정지... intervalId: ${intervalId.current}`);
  };

  return (
    <>
      <p>자동 카운트: {count}</p>
      <button onClick={startCounter}>시작</button>
      <button onClick={stopCounter}>정지</button>
    </>
  );
}

export default ManualCounter;

c) useRef 대신 useState를 사용할 수 있으나 구별해서 쓰자.

import React, { useState } from "react";

function ManualCounter() {
  const [count, setCount] = useState(0);
  const [intervalId, setIntervalid] = useState(null);

  const startCounter = () => {
    setIntervalid(setInterval(() => setCount((count) => count + 1), 1000));
  };

  const stopCounter = () => {
    clearInterval(intervalId);
  };

  return (
    <>
      <p>자동 카운트: {count}</p>
      <button onClick={startCounter}>시작</button>
      <button onClick={stopCounter}>정지</button>
    </>
  );
}

export default ManualCounter;

-> 이런 식으로 state로 선언하여, 문제를 해결할 수 있지만, 이 예제의 경우 useRef를 사용하는 것이 좋은 이유는 intervalid는 화면에 나타나지 않기 때문이다. 화면에 나타나지 않아 intervalid가 바뀌었을 때 리렌더링이 필요없다. 화면에 나타나는 경우는 useState, 화면에 나타나지 않는 경우는 useRef를 쓰면 좋을 것 같다는 개인적인 생각을 덧붙여본다.


3) useRef를 사용하여 DOM선택

a) useRef로 DOM 선택해보기

  • 컴포넌트의 태그(tag)에 직접 접근하고 싶을 때 useRef를 사용한다. 자바스크립트에서 DOM element를 가지고 올 때 querySelectorgetElementById를 사용하지만 React에서는 useRef를 사용한다.
import React, { useState, useRef } from "react";

const TryUseRefDom = () => {
  const [emailValue, setEmailValue] = useState(""); // email state 값
  const emailInput = useRef(null); // email input에 대한 useRef

  const inputCheck = (e) => {
    e.preventDefault();
    if (emailInput.current.value === "") {
      alert("이메일을 입력해주세요");
      emailInput.current.focus();
      return;
    }
    setEmailValue(emailInput.current.value);
  };

  return (
    <form>
      <label>
        이메일 : <input type="email" ref={emailInput} />
      </label>
      <button type="submit" style={{ width: "100px" }} onClick={inputCheck}>
        로그인
      </button>
      <br />
      <span>입력한 이메일 : {emailValue}</span>
    </form>
  );
};

export default TryUseRefDom;

b) useRef는 남용하지 않는 것이 좋다

  • 먼저 ref를 사용하지 않고도 다른 기능을 사용하여서 구현할 수 있는지 고려한 후 사용하자
  • React의 장점인 선언형 프로그래밍 원칙과 배치되기 때문에, 조심해서 사용해야 한다. (명령형 vs 선언형 프로그래밍)

    명령형 프로그래밍은 컴퓨터가 수행할 명령들을 순서대로 써 놓은 것이고, 선언형 프로그래밍은 프로그램이 어떤 방법으로 해야 하는지를 나타내기보다 무엇과 같은지를 설명한다.

import React, { useState, useRef } from "react";

const TryUseRefDom = () => {
  const [emailValue, setEmailValue] = useState(""); // email state 값

  const inputCheck = (e) => {
    e.preventDefault();
    if (emailValue === "") {
      alert("이메일을 입력해주세요");
      e.target[0].focus();
      return;
    }
    alert("로그인 완료");
  };

  const handleChange = (e) => {
    setEmailValue(e.target.value);
  };

  return (
    <form onSubmit={inputCheck}>
      <label>
        이메일 :
        <input type="email" value={emailValue} onChange={handleChange} />
      </label>
      <button type="submit" style={{ width: "100px" }}>
        로그인
      </button>
      <br />
      <span>입력한 이메일 : {emailValue}</span>
    </form>
  );
};

export default TryUseRefDom;

-> a의 코드는 비제어 컴포넌트, b의 코드는 제어컴포넌트 (React: 제어 컴포넌트와 비제어 컴포넌트의 차이점)
+) 신뢰가능한 단일 출처: 입력한 데이터 상태와 저장된 상태가 같은 것을 말하고 사용자가 입력한 값과 저장되는 값이 실시간으로 동기화 되는 것을 말함.

참고자료
React Hooks: useRef 사용법

profile
🙌꿈꾸는 프론트엔드 개발자 신은수입니당🙌

0개의 댓글