본문 바로가기
웹/React

[React] useRef로 컴포넌트의 변수 생성하기

by 천무지 2024. 7. 18.
반응형

 

 

useRef는 useReference의 줄임말로 컴포넌트 내부에 새로운 레퍼런스 객체를 생성해준다.

 

그리고 이렇게 생성한 레퍼런스 객체는 컴포넌트 내부의 변수로써 일반적인 값들을 저장할 수 있다.

 

이는 useState와 같아보인다.

 

하지만 값이 변경되면 컴포넌트를 리렌더링하는 useState와 달리 useRef는 어떤 경우에도 리렌더링을 발생시키지 않는다.

 

따라서 렌더링에 영향을 미치고 싶지 않은 변수를 생성할 때 useRef를 이용하게 된다.

 

또한 useRef를 이용하면 컴포넌트가 렌더링하는 특정 DOM 요소에 접근할 수 있고 그 요소를 조작할 수 있다.

 

예를 들어서 어떤 요소에 focus를 주거나, 어떤 요소의 스타일을 변경할 수 있다는 것이다.

 

먼저 지지난 시간부터 이어서 만들었던 Register.js에 useRef를 이용해서 객체 변수를 하나 만들어보자.

 

그리고 콘솔로그를 통해서 무엇이 출력되는지 확인해보자.

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

//간단한 회원가입 폼
//1. 이름
//2. 생년월일
//3. 국적
//4. 자기소개

const Register = () => {
  const [input, setInput] = useState({
    name: "",
    birth: "",
    country: "",
    bio: "",
  });
  
  const refObj = useRef();
  console.log(refObj);

  const onChange = (e) => {
    setInput({
      ...input,
      [e.target.name]: e.target.value,
    });
  };

  return (
    <div>
      <div>
        <input
          name="name"
          value={input.name}
          onChange={onChange}
          placeholder={"이름"}
        />
      </div>

      <div>
        <input
          name="birth"
          value={input.birth}
          type={"date"}
          onChange={onChange}
        />
      </div>

      <div>
        <select name="country" value={input.country} onChange={onChange}>
          <option></option>
          <option value={"kr"}>한국</option>
          <option value={"us"}>미국</option>
          <option value={"uk"}>영국</option>
          <option value={"ch"}>중국</option>
        </select>
      </div>

      <div>
        <textarea name="bio" value={input.bio} onChange={onChange} />
      </div>
    </div>
  );
};

export default Register;

 

출력 결과를 확인해보면 current라는 property를 갖는 객체가 출력된다. 이 객체가 바로 방금 생성한 레퍼런스 객체다.

 

결국 레퍼런스 객체란 current라는 property에 보관할 값을 담아두기만 하는 단순한 자바스크립트 객체이다.

 

그래서 useRef로 객체를 생성할때 초기값으로 0을 넣어주면 current에 0이라는 초기값을 저장하고 있는 레퍼런스 객체가 출력된다.

그리고 만약 레퍼런스 객체에 담겨져 있는 값을 사용하고 싶다면 래퍼런스 객체에 점 표기법으로 current에 접근하면 된다.

//Register.js
.
.
.
  const refObj = useRef(0);
  console.log(refObj);
  console.log(refObj.current);
  .
  .
  .

 

그리고 useRef로 생성한 래퍼런스 객체는 useState와 달리 보관하고 있는 값이 변경되었다고 컴포넌트의 리렌더링이 발생하지 않는다.

 

그래서 컴포넌트의 리턴문 맨 위에 버튼 태그를 넣고 ref + 1이 되도록 이벤트 핸들러를 설정해주고 결과를 확인해보자.

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

//간단한 회원가입 폼
//1. 이름
//2. 생년월일
//3. 국적
//4. 자기소개

const Register = () => {
  const [input, setInput] = useState({
    name: "",
    birth: "",
    country: "",
    bio: "",
  });

  const refObj = useRef(0);
  console.log(refObj);
  console.log(refObj.current);

  const onChange = (e) => {
    setInput({
      ...input,
      [e.target.name]: e.target.value,
    });
  };

  return (
    <div>
      <button
        onClick={() => {
          refObj.current++;
          console.log(refObj.current);
        }}
      >
        ref + 1
      </button>
      <div>
        <input
          name="name"
          value={input.name}
          onChange={onChange}
          placeholder={"이름"}
        />
      </div>

      <div>
        <input
          name="birth"
          value={input.birth}
          type={"date"}
          onChange={onChange}
        />
      </div>

      <div>
        <select name="country" value={input.country} onChange={onChange}>
          <option></option>
          <option value={"kr"}>한국</option>
          <option value={"us"}>미국</option>
          <option value={"uk"}>영국</option>
          <option value={"ch"}>중국</option>
        </select>
      </div>

      <div>
        <textarea name="bio" value={input.bio} onChange={onChange} />
      </div>
    </div>
  );
};

export default Register;

 

보다시피 onClick 이벤트만 계속 실행이 되지만 이벤트 헨들러 밖에 있는 객체를 출력하는 콘솔로그와 래퍼런스 객체의 current 값을 출력하는 코드는 실행되지 않은 것을 볼 수 있다.

 

즉, 리렌더링이 발생하지 않은 것을 볼 수 있다.

 

따라서 래퍼런스 객체는 컴포넌트 내부에서 렌더링에 영향을 미치지 않아야 하는 변수를 생성할 때 활용할 수 있다.

 

 

 

이번에는 레퍼런스 객체를 이용해서 현재 Register 컴포넌트가 랜더링하고 있는 4개의 form에 사용자가 얼마나 많은 변경을 일으켰는지 수정 횟수를 카운트 하는 기능을 만들어보자

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

//간단한 회원가입 폼
//1. 이름
//2. 생년월일
//3. 국적
//4. 자기소개

const Register = () => {
  const [input, setInput] = useState({
    name: "",
    birth: "",
    country: "",
    bio: "",
  });

  const countRef = useRef(0);

  const onChange = (e) => {
    countRef.current++;
    console.log(countRef.current);
    setInput({
      ...input,
      [e.target.name]: e.target.value,
    });
  };

  return (
    <div>
      <div>
        <input
          name="name"
          value={input.name}
          onChange={onChange}
          placeholder={"이름"}
        />
      </div>

      <div>
        <input
          name="birth"
          value={input.birth}
          type={"date"}
          onChange={onChange}
        />
      </div>

      <div>
        <select name="country" value={input.country} onChange={onChange}>
          <option></option>
          <option value={"kr"}>한국</option>
          <option value={"us"}>미국</option>
          <option value={"uk"}>영국</option>
          <option value={"ch"}>중국</option>
        </select>
      </div>

      <div>
        <textarea name="bio" value={input.bio} onChange={onChange} />
      </div>
    </div>
  );
};

export default Register;

이벤트 헨들러의 발생이 곧 수정횟수 이기 때문에 onChange 함수가 호출되면 래퍼런스 객체의 current값이 증가하도록 onChange 함수 내부에 작성해주었다.

 

 

 

이번에는 새로운 레퍼런스 객체를 만들어서 Register 컴포넌트가 렌더링하고 있는 DOM 요소를 직접 조작해보자.

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

//간단한 회원가입 폼
//1. 이름
//2. 생년월일
//3. 국적
//4. 자기소개

const Register = () => {
  const [input, setInput] = useState({
    name: "",
    birth: "",
    country: "",
    bio: "",
  });

  const countRef = useRef(0);
  const inputRef = useRef();

  const onChange = (e) => {
    countRef.current++;
    console.log(countRef.current);
    setInput({
      ...input,
      [e.target.name]: e.target.value,
    });
  };

  const onSubmit = () => {
    if (input.name === "") {
      console.log(inputRef.current);
    }
  };

  return (
    <div>
      <div>
        <input
          ref={inputRef}
          name="name"
          value={input.name}
          onChange={onChange}
          placeholder={"이름"}
        />
      </div>

      <div>
        <input
          name="birth"
          value={input.birth}
          type={"date"}
          onChange={onChange}
        />
      </div>

      <div>
        <select name="country" value={input.country} onChange={onChange}>
          <option></option>
          <option value={"kr"}>한국</option>
          <option value={"us"}>미국</option>
          <option value={"uk"}>영국</option>
          <option value={"ch"}>중국</option>
        </select>
      </div>

      <div>
        <textarea name="bio" value={input.bio} onChange={onChange} />
      </div>

      <button onClick={onSubmit}>제출</button>
    </div>
  );
};

export default Register;

현재 제출 버튼을 눌렀을 때 만약 이름을 입력하지 않았다면 이름을 입력 받는 인풋 태그에 포커스가 되도록 하고 싶다.

 

인풋 태그에 포커스를 주려면 해당 DOM 요소에 접근할 수 있어야 한다. 

 

이떄 래퍼런스 객체를 사용한다.

 

인풋 태그의 property에 새롭게 생성한 래퍼런스 객체인 inputRef를 추가해 주자 (ref = {inputRef})

 

이렇게 하면 인풋 태그가 렌더링하는 DOM요소가 inputRef에 저장이 된것이다.

 

실제로 콘솔로그로 확인해보면 인풋 태그 DOM요소가 출력되는 것을 알 수 있다.

 

그리고 이번에는 우리하 하고 싶었던 포커스를 줘보자.

 

현재 inputRef.current에 현재 우리가 접근하고자 하는 DOM 요소가 저장되어있다는 것을 알았으니 여기서 focus()라는 메소드를 호출해주면 된다.

//Register.js
.
.
.
  const onSubmit = () => {
    if (input.name === "") {
      inputRef.current.focus();
    }
  };
.
.
.

 

그러면 이름 인풋에 아무값도 입력하지 않았을 때 제출을 누르면 이름 인풋이 focus 되는 것을 확인할 수 있다.

 

 

 

사실 값이 변경될 때 리렌더링을 유발하지 않고 싶다면 useRef라는 기능을 이용하지 않고 그냥 자바스크립트 변수를 이용하면 될 것이라는 생각을 할 수 있다.

 

실제로 자바스크립트 변수를 useRef 대신 사용해보면 이유를 바로 알 수 있다.

 

새로운 count라는 변수를 생성하고 countRef 대신 사용해보자.

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

//간단한 회원가입 폼
//1. 이름
//2. 생년월일
//3. 국적
//4. 자기소개

const Register = () => {
  const [input, setInput] = useState({
    name: "",
    birth: "",
    country: "",
    bio: "",
  });

  const countRef = useRef(0);
  const inputRef = useRef();

  let count = 0;

  const onChange = (e) => {
    count++;
    console.log(count);
    setInput({
      ...input,
      [e.target.name]: e.target.value,
    });
  };

  const onSubmit = () => {
    if (input.name === "") {
      inputRef.current.focus();
    }
  };

  return (
    <div>
      <div>
        <input
          ref={inputRef}
          name="name"
          value={input.name}
          onChange={onChange}
          placeholder={"이름"}
        />
      </div>

      <div>
        <input
          name="birth"
          value={input.birth}
          type={"date"}
          onChange={onChange}
        />
      </div>

      <div>
        <select name="country" value={input.country} onChange={onChange}>
          <option></option>
          <option value={"kr"}>한국</option>
          <option value={"us"}>미국</option>
          <option value={"uk"}>영국</option>
          <option value={"ch"}>중국</option>
        </select>
      </div>

      <div>
        <textarea name="bio" value={input.bio} onChange={onChange} />
      </div>

      <button onClick={onSubmit}>제출</button>
    </div>
  );
};

export default Register;

 

값을 수정할 때마다 1씩 증가해야 하지만 1만 반복되서 나오는 것을 알 수 있다.

 

즉, count 변수의 값이 1로 고정되어 있다는 것이다.

 

우리가 인풋에 값을 입력하게 되면 onChange 이벤트 헨들러가 실행이 된다. 이 이벤트 헨들러 안에는 상태 변화 함수를 호출하여 state의 값을 변경하도록 하고 있다. 

 

따라서 해당 컴포넌트의 리렌더링이 발생한다. 

 

리렌더링이 발생함에 따라서 count는 0에서 1로 증가되자마자 다시 0으로 리셋된다.

 

하지만 useRef나 useState로 만든 특수한 변수들은 컴포넌트가 리렌더링 된다고 해도 리셋되지 않는다.

 

왜냐하면 애초에 내부적으로 그렇게 설계가 되어있다.

 

 

반응형

' > React' 카테고리의 다른 글

[React] useEffect 사용하기  (0) 2024.07.26
[React] React Hooks  (0) 2024.07.18
[React] State로 사용자 입력관리하기2  (0) 2024.07.18
[React] State로 사용자 입력 관리하기1  (0) 2024.07.18
[React] State를 props로 전달하기  (0) 2024.07.18