[React] - useRef

Minji Kim·2022년 9월 13일
0
post-thumbnail

React Hook 중에 이상하게 많이 접했던 친구 중에 하나..

페이지 구성할 때 array의 각 id를 사용해서 수정 및 삭제하는 기능을 주로 만들다보니 많이 접하게 되었다.

useRef는 다른 훅과 비교했을 때, 좀 쉬워(?)보이기도 해서 제대로 공부를 안 하고 사용했다가 렌더링 이슈를 접하게 되면서 딥하게 공부해야겠다 생각이 들었다.

공부를 하면서 TypeScript와 같이 사용될 때, 정확한 동작 원리 또한 이해해야한다는 것도 느꼈다.

잘 알지도 못하며 사용했던 나를 반성하며 useRef를 알아보고자 한다.

왜 사용하는가?

본론부터 바로 말하자면, 불필요한 Re-rendering을 하지 않고 값만 보존하기 위해 사용한다. 쉽게 말해, 저장공간이다.

const refContainer = useRef(initialValue);

React의 불필요한 Re-rendering 이슈는 개발자들이 최적화하기 위해 끊임없이 고민하는 주제였고, 이에 대한 해결책으로는 대표적으로 React Hook이 있다.

React의 Re-rendering

잠시 React의 Re-rendering 조건에 대해 살펴보자면, 크게 3가지로 구분된다.

  1. 자신의 state가 변경될 때
  2. 부모 컴포넌트로 받은 props가 변경될 때
  3. 부모 컴포넌트가 렌더링될 때

자주 쓰는 React hook에 대한 더 자세한 내용은 여기를 참고해주세요.

렌더링의 의미는 내부의 변수들이 담고 있는 기존에 저장한 값들이 초기화되고, 함수 로직이 재실행됨을 의미한다.

하지만 렌더링되더라도 기존에 저장한 값들을 그대로 보존해야 하는 경우가 있다.

이 때 ! useRef 함수를 사용하는 것이다.

그래서 useRef가 정확히 뭔데 ?

useRef 반환 타입

정확히 이해하기 위해 useRef의 반환 타입을 살펴보자.

interface MutableRefObject<T> {
        current: T;
}

interface RefObject<T> {
        readonly current: T | null;
}

useRef는 .current 프로퍼티를 가지고 있는 하나의 새로운 객체를 생성하며 그저 초기값을 저장할 뿐이다.

그럼 왜 굳이 current 객체를 만들면 되지, useRef를 사용할까 ?

useRef와 직접 {current: …} 이렇게 객체 자체를 생성하는 것의 차이점은 앞서 계속 강조한 내용과 같이 useRef는 렌더링이 되더라도 값을 저장하고 있기 때문에 동일한 ref 객체를 제공한다는 것..

Point
✅  useRef는 리렌더링을 발생시키지 않는다. 다른 조건으로 렌더링되더라도 값을 보존(저장)하고 싶을 때 사용한다.
쉽게 말해서, useRef는 .current 프로퍼티에 변경 가능한 값을 담고 있는 ‘상자📦'와 같다.

언제 사용하는가?

useRef를 사용하는 건 크게 두 가지로 나눌 수 있다.

  1. 리렌더링하지 않고 변경 가능한 값을 저장하고 싶을 때
  2. DOM 엘리먼트에 직접 액세스할 때

useRef 동작원리

useRef 제대로 사용하기 위해 동작 원리를 파헤쳐보면,

//인자 값이 있을 때, 인자 타입과 제네릭 타입이 일치하는 경우
function useRef<T>(initialValue: T): MutableRefObject<T>;

//인자 값이 있을 때, 인자 타입이 null을 허용하는 경우
function useRef<T>(initialValue: T|null): RefObject<T>;

//제네릭 타입이 undefined인 경우
function useRef<T = undefined>(): MutableRefObject<T | undefined>;

어떤 인자가 들어오느냐에 따라 다르게 반환이 된다.

앞서 언급한 각각의 사용 케이스를 예로 들면,

  1. [예시 코드1] 리렌더링하지 않고 변경 가능한 값을 로컬 변수에 저장하고 싶을 때
import { useRef, useState } from 'react'

export interface IList {
  id: number
  text: string
  done: boolean
}

const refExample = () => {
	const [inputList, setInputList] = useState<IList[]>([])
	const listId = useRef<number>(0)
	
	const onCreate = () => {
		setInputList([
	    ...inputList,
	    {
	      id: listId.current,
	      text: item,
	      done: false,
	    },
	  ])
	  listId.current += 1
	}
..
..
}

export default refExample

input을 통해 IList[] 형식으로 데이터가 들어온다고 가정했을 때, input된 데이터가 추가될 때 마다 id 또한 누적해서 추가하고 싶다면 current 프로퍼티를 이용하여 1씩 값을 더할 수 있다.

이 경우에는 useRef이 값을 조정할 수 있도록 반환 타입이 MutableRefObject<T> 요 녀석이어야만 하기 때문에 인자 타입과 제네릭 타입이 일치해야만 한다.

  1. [예시 코드2] DOM 엘리먼트에 직접 액세스할 때
import { useRef } from 'react'
import './App.css'

const App = () => {
  const inputEl = useRef<HTMLInputElement>(null)

  const onBtnClick = () => {
    inputEl.current?.focus()
  }
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onBtnClick}>input 초점</button>
    </>
  )
}

export default App

input 태그의 focus를 맞추거나, 값을 변경하고자 할 때 주로 사용한다. 이 경우에는 인자 타입이 null을 허용하는 경우에 해당한다. (2번째 케이스)
코드의 예시는 버튼을 누르면 input 태그의 값을 수정하지 않고 focus를 맞추는 것으로 실습해봤다.
엘리먼트에 접근할 때는 ref 함수를 사용하여 해당 엘리먼트로 접근 할 수 있다.

useRef의 오류

그렇다면 [예시 코드2]를 다음과 같이 값을 수정하는 코드로 변경한다면 어떻게 될까 ?

..
  const inputEl = useRef<HTMLInputElement>(null)

  const onBtnClick = () => {
  inputEl.current = "react hook"
}
..

이런 오류가 발생한다.

다시 한 번 반환 타입을 자세히 살펴보자.

  interface MutableRefObject<T> {
        current: T;
}
  
  interface RefObject<T> {
        readonly current: T | null;
}

MutableRefObject는 이름에도 알 수 있다시피 값을 변경할 수 있으며 RefObjectreadonly로서 수정이 불가능하다는 것을 알 수 있다.
첫 번째 예시코드에서는 인자 타입과 제네릭 타입이 같으므로 current 프로퍼티 값을 수정할 수 있었지만, 변경한 두 번째 예시코드에서는 인자 값이 null이므로 값을 current 프로퍼티는 읽기 전용으로 수정할 수 없는 것이다.

값을 변경하고 싶다면 onBtnClick 컴포넌트를 다음과 같이 코드를 변경해보자.

  const onBtnClick = () => {
    inputEl.current!.value = 'react hook'
  }

요렇게 코드를 작성하면 정상 작동한다. 그 이유는 무엇일까 ?

current 프로퍼티가 객체라는 사실에 초점을 맞추면 된다. current 프로퍼티 자체는 수정이 불가하지만 current의 하위 프로퍼티 value는 수정이 가능하다는 것이다.

따라서 useRef의 initialValue가 null일 수 밖에 없고, 값을 수정해야한다면 하위 프로퍼티를 사용하면 오류 없이 정상 작동시킬 수 있다.

Point
✅  current 프로퍼티만 읽기 전용으로, current 프로퍼티의 하위 프로퍼티는 수정 가능하다. readonly는 얕은 객체복사 이기 때문이다.

얕은 복사와 깊은 복사에 대한 더 자세한 내용은 추후 다른 주제로 포스팅하려고 한다.

참고

https://ko.reactjs.org/docs/hooks-reference.html#useref
https://ko.reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables
https://velog.io/@rara-record/TypeScript-React%EC%97%90%EC%84%9C-useRef

profile
애기코더 응애

0개의 댓글