useRef 첫 번째 기능: Component의 Dom 취득하기

시험해볼 소스코드는 다음과 같습니다.

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

function InputSample() {
  const [inputs, setInputs] = useState({name: ''});
  const nameInput = useRef();

  const { name, nickname } = inputs; // 비구조화 할당을 통해 값 추출

  const onChange = e => {
    const { value, name } = e.target; // 우선 e.target 에서 name 과 value 를 추출
    setInputs({
      ...inputs, // 기존의 input 객체를 복사한 뒤
      [name]: value // name 키를 가진 값을 value 로 설정
    });
  };

  const onReset = () => {
    setInputs({name: ''});
    nameInput.current.focus();
  };

  return (
    <div>
      <input
        name="name"
        placeholder="이름"
        onChange={onChange}
        value={name}
        ref={nameInput}
      />
      <button onClick={onReset}>초기화</button>
      <div>
        <b>값: </b>
        {name} ({nickname})
      </div>
    </div>
  );
}

export default InputSample;

useRef에 정말 컴포넌트의 Dom이 들어와있는지 아닌지를 확인하기 위한 목적의 어플입니다. 상단부에서 useRef를 선언하고, 컴포넌트에 useRef에 의해 생성된 인스턴스를 붙여줍니다. ref={nameInput}와 같이 컴포넌트에 ref={인스턴스}의 방법으로 useRef를 컴포넌트에 부착시킬 수 있습니다.

어플의 요건은 단순합니다. 하나의 input이 있고, reset버튼을 누르면 아래와 같이 작동합니다.

  1. input state가 초기화되고, 컴포넌트가 re-render
  2. input에 포커싱

 

 

 

 

위의 그림처럼 초기화버튼을 클릭하자 input에 focusing이 된 것을 볼 수 있습니다.(포커싱 되면 까만 테두리로 표현돼요)

 

 

ref.current에 정말 dom이 들어와있는지, 개발자 도구로 확인해봅시다. input의 dom이 잘 들어와 있네요. 따라서 ref.current를 제어해서 dom 내부 함수를 사용할 수 도 있고, Dom내부의 값을 빼올 수 도 있습니다.

 

useRef 두 번째 기능: re-render시에도 격납한 값이 불변

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

function ManualCounter() {
  const [count, setCount] = useState(0)
  const intervalId = useRef(null)
  console.log(`랜더링... count: ${count}`)

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

위 어플의 전제적인 흐름은 다음과 같습니다.

  1. 시작 버튼 클릭
  2. 1초에 한번씩 count state를 업데이트
  3. state가 업데이트 될 때마다 ManualCounter 컴포넌트 re-rendering
    (위의 과정 반복..)
     
    위의 어플을 기동시키면 다음과 같은 console을 얻을 수 있습니다.
시작... intervalId: 4
랜더링... count: 1
랜더링... count: 2
랜더링... count: 3
랜더링... count: 4
정지... intervalId: 4
시작... intervalId: 5
랜더링... count: 5
랜더링... count: 6
랜더링... count: 7
랜더링... count: 8
정지... intervalId: 5

콘솔에서 시작할 때의 intervalId와 정지할 때의 intervalId가 동일한 것을 알 수 있습니다. 시작시 intervalId.current에 격납해 두었던 intervalId가 컴포넌트가 여러번 re-render된 뒤에도 값이 초기화되지 않고 intervalId.current에 저장되어 있었음을 의미하죠.

+ Recent posts