몽환화

[React] useRef 물고뜯기 본문

Front/React

[React] useRef 물고뜯기

hyeii 2024. 5. 1. 17:47

useRef

렌더링에 필요하지 않은 값을 참조할 수 있는 React Hook

컴포넌트의 최상위 레벨에서 useRef를 호출하여 ref를 선언

const ref = useRef(initialValue)

 

컴포넌트가 일부 정보를 “기억”하고 싶지만, 해당 정보가 렌더링을 유발하지 않도록 하려면 ref를 사용하세요.

 

매개변수 initialValue : ref 객체의 current 프로퍼티 초기 설정값, 초기 렌더링 이후부터는 무시됨

반환값 ref : 단일 프로퍼티를 가진 다음과 같은 객체를 반환한다

{
  current: 0 // useRef에 전달한 값 
}

current : 처음에는 전달한 initialValue로 설정된다

 


 

ref.current 프로퍼티를 통해 해당 ref의 current 값에 접근할 수 있다.

=> 의도적으로 변경할 수 있으므로 읽고 쓸 수 있다. 리액트가 추적하지 않는다

 

  • ref.current 프로퍼티는 state와 단리 변이할 수 있다. 그러나 렌더링에 사용되는 객체를 포함하는 경우 해당 객체를 변이해서는 안된다
  • ref.current 프로퍼티를 변경해도 컴포넌트를 다시 렌더링하지 않는다. ref는 일반 자바스크립트 객체이다! 리액트사용자가 언제 변경했는지 모른다.
  • 초기화를 제외하고는 렌더링 중에 ref.current를 쓰거나 읽지 말아야 한다!

 

그러니까 컴포넌트가 렌더링 될 때 생성이 되지만 얘가 변경된다고 해서 리렌더링이 발생되지는 않는게 useRef

state와 마찬가지로 ref도 리액트의 리렌더링에 의해 유지되지만 state는 설정되면 컴포넌트가 다시 렌더링되고, ref를 변경하면 다시 렌더링되지 않는다.

 

 

useRef는 왜 필요한가,,, 

그냥 함수 외부에서 값을 선언해서 관리하면 안되나?

=>

  1. 렌더링 되기 전에도 기본적으로 해당 값을 지니기 때문에 메모리에 불필요한 내용이 들어가게 된다. 
  2. 컴포넌트가 여러번 생성된다면 각 컴포넌트에서 가리키는 값이 모두 동일해진다.
  3. 대부분의 경우 컴포넌트 인스턴스 하나당 하나의 값을 필요로 한다.
import { useRef } from 'react';

	function MyComponent() {
	const intervalRef = useRef(0);
	const inputRef = useRef(null);
	// ...

 

ref는 변경해도 리렌더링을 촉발하지 않기 때문에 시각적 출력에 영향을 미치지 않는 정보를 저장하는 데 적합하다

ref 내부의 값을 업데이트하려면 current 프로퍼티를 수동으로 변경해야 한다

 

화면에 표시되는 정보를 저장하는데는 ref보다는 state가 적합하다 

import { useRef } from "react";

export default function RefCounter() {
  let ref = useRef(0);

  function handleClick() {
    ref.current = ref.current + 1;
    alert("You clicked " + ref.current + " times!");
  }

  return (
    <div>
      <button onClick={handleClick}>Click me!</button>
      <div>{ref.current}</div>
    </div>
  );
}

버튼을 클릭하면 You clicked * times! 에서 수가 계속 증가한다 => 그 시점의 ref.current이다

그러나 화면상의 {ref.current} 는 초기 렌더링 당시의 값이므로 0을 나타내며, 버튼을 누른다고 해도 리렌더링이 되지 않아 계속 0인 상태를 유지한다

 

그러니까

ref.current의 값이 변하지 않는다는게 아니다

쟤도 계속 변하지 우리가 버튼을 클릭할 때 마다 alert에 뜨는 값은 변화한다 그때의 ref.current를 띄워주니까

그런데 리렌더링이 되는게 아니니까 그냥 화면에는 초기값이 그대로 있는거야

화면은 그냥 변화가 없는거


 

ref로 DOM 조작하기

초기값이 null인 ref 객체를 생성한다

const ref = useRef(null)

 

(선언을 했어도 렌더링 하기 전이면 ref.current를 찍었을 때 undefined가 나온다!)

 

ref객체를 ref속성으로 조작하려는 DOM 노드의 JSX에 전달

return <input ref={inputRef} />

 

리액트가 DOM 노드를 생성하고 화면에 그린 후 리액트는 ref 객체의 current 프로퍼티를 DOM 노드로 설정함

focus() 같은 메서드 호출 가능!

function handleClick() {
    inputRef.current.focus();
  }

 

노드가 화면에서 제거되면 리액트는 current 프로퍼티를 다시 null로 설정함 

 


 

커스텀 컴포넌트에선 ref를 보낼 수 없다

const inputRef = useRef(null);

return <MyInput ref={inputRef} />;

=> 에러 발생!

이때는 저 MyInput 컴포넌트를 

import { forwardRef } from 'react';

const MyInput = forwardRef(({ value, onChange }, ref) => {
  return (
    <input
      value={value}
      onChange={onChange}
      ref={ref}
    />
  );
});

export default MyInput;

 

이렇게 forwartRef로 감싸면 해결된다

 

리액트의 모든 갱신은 다음과 같은 두 단계이다

- 렌더링 : 리액트 화면에 무엇을 그려야하는지 알아내도록 컴포넌트를 호출한다

- 커밋 : 리액트는 변경사항을 DOM에 적용한다

일반적으로 렌더링하는 중에 ref에 접근하는 것을 원하지 않는다. 첫 렌더링에서는 DOM노드가 아직 생성되지 않아 ref.current는 null 인 상태! 

 

리액트는 ref.current를 커밋 단계에서 설정을 한다. 

 

대부분의 ref에 대한 접근은 이벤트 핸들러에서 일어난다.

 

useState와 동일하게 컴포넌트 내부에서 렌더링이 일어나도 변경 가능한 상태값을 저장한다는 공통점이 있다

그러나

  • useRef는 반환값인 객체 내부에 있는 currendt로 값에 접근 또는 변경할 수 있다 
  • useRef는 그 값이 변하더라도 렌더링을 발생시키지 않는다

 

=> useRef로 useState를 흉내내어도 렌더링은 발생하지 않는다

 

 

useRef는 DOM 뿐만 아니라 어떤 값이든 저장할 수 있는 일반적인 자바스크립트 객체이다. 

 

일반적인 프로그래밍 언어는 heap 영역과 stack 영역에서 메모리를 관리한다

stack 공간은 함수가 실행될 때 메모리에 할당됐다가 종료되면서 한꺼번에 해제된다

반면 heap은 전역변수와 참조타입의 변수를 할당하고 가비지 컬렉터를 이용해 사용하지 않는 메모리를 해제시킨다.

즉 우리가 자바스크립트 객체로 만드는 변수들은 모두 heap 공간에 할당되었다가 해제된다

 

useRef()는 일반적인 자바스크립트 객체로 heap영역에 저장되는 변수이다

heap영역에 저장되어있기 때문에 어플리케이션이 종료되거나 가비지 컬렉팅될 때 까지 참조할때마다 같은 메모리 값을 가진다고 할 수 있다

값이 변경되어도 리렌더링이 되지 않는다

같은 메모리 주소를 갖고있기 때문에 자바스크립트의 ===연산이 항상 true를 반환하여 변경사항을 감지하지 못한다. 

 

 

 

 

렌더링을 발생시키지 않고 원하는 상태값을 저장할 수 있다는 특징 활용 => useState의 이전 값을 저장한다 

 

그렇다면 state가 업데이트 되어서 리렌더링이 발생했을 때에 useRef의 값은 유지되는가?

=> 유지된다.

 

ref state
useRef(initialValue)  { current: initialValue } 을 반환 useState(initialValue) 은 state 변수의 현재 값과 setter 함수 [value, setValue] 를 반환
state를 바꿔도 리렌더 되지 않는다 state를 바꾸면 리렌더 된다
Mutable-렌더링 프로세스 외부에서 current 값을 수정 및 업데이트할 수 있음 ”Immutable”—state 를 수정하기 위해서는 state 설정 함수를 반드시 사용하여 리렌더 대기열에 넣어야 함
렌더링 중에는 current 값을 읽거나 쓰면 안 된다  언제든지 state를 읽을 수 있음. 그러나 각 렌더마다 변경되지 않는 자체적인 state의 snapshot이 있음 

 

 

 

참고자료 

 

도서 :: 모던 리액트 Deep Dive https://product.kyobobook.co.kr/detail/S000210725203

https://ko.react.dev/learn/escape-hatches

https://ko.react.dev/reference/react/useRef

https://dev.to/dylanju/useref-3j37?fbclid=IwAR0fl7xMjn_Hp6sImCU-EQt4gJ0ob_YY6hS3cwn4ARyClTUYD2KN0R6X-O0&source=post_page-----f0359ad23f3b--------------------------------

https://flyingsquirrel.medium.com/react-%EC%BD%94%EB%93%9C-%EA%B9%8C%EB%B3%B4%EA%B8%B0-useref%EB%8A%94-dom%EC%97%90-%EC%A0%91%EA%B7%BC%ED%95%A0-%EB%95%8C-%EB%BF%90%EB%A7%8C-%EC%95%84%EB%8B%88%EB%9D%BC-%EB%8B%A4%EC%96%91%ED%95%98%EA%B2%8C-%EC%9D%91%EC%9A%A9%ED%95%A0-%EC%88%98-%EC%9E%88%EC%96%B4%EC%9A%94-f0359ad23f3b

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

[React] Recoil 뜯어보기  (0) 2024.05.22
[React] useLayoutEffect, useDebugValue  (0) 2024.05.06
[React] useImperativeHandle 물고뜯기  (0) 2024.05.06
[React] useReducer 물고뜯기  (0) 2024.05.03
[React] useContext 물고뜯기  (0) 2024.05.02