useRef 사용 동기
최근 회사에서 jsmpeg 라이브러리를 사용하여 소켓 연결을 통해 IP 카메라를 재생하는 기능을 개발하였다.
jsmpeg을 static으로 import 하여 개발하던 중, 프론트 단에서 window.location.hostname:{포트번호} 주소로 소켓을 연결하는 로직에서 "ReferenceError: window is not defined" 에러가 발생하였다.
이 오류는 Next.js의 서버 사이드 렌더링(SSR) 환경에서 window 객체에 접근하려고 시도하면 발생하는 것이다. Next.js는 초기 렌더링 시에 서버에서 컴포넌트를 생성하기 때문에, 서버에서는 window 객체와 같은 브라우저 전용 객체에 접근할 수 없다. 따라서 useEffect, useState를 사용하여 렌더링 초기에 dynamic import로 jsmpeg 라이브러리를 비동기적으로 불러온 후, state에 소켓 주소를 구현하는 방식을 택했다.
jsmpeg 라이브러리는 위와 같은 방법으로 연결된 socket 주소와 canvas 태그를 이용하여 값을 할당해주면 html의 canvas 태그로 지정한 곳에 실시간으로 ip camera 영상이 재생된다.
그러나 이 방법은 useState의 특성으로 인해 차례대로 실행 순서를 보장 받을 수 없으므로, player에 올바른 소켓 주소를 할당할 수 없는 현상이 생긴다. 따라서 문제를 해결하기 위해서는 (1) jsmpeg 라이브러리를 정상적으로 불러온 후, (2) 웹소켓을 정상적으로 연결하고 (3) 만들어진 소켓값을 jsmpeg.default에 할당하는 순서가 보장이 되어야 한다.그렇다고 socket 변수를 전역으로 빼 버리면, 리액트의 특성상, new Websocket이 세 번 실행되어, 3개의 소켓 연결이 생기게 된다.
여러 시행착오를 겪은 끝에 나는 useRef를 사용하여 순서와 변수값을 보장, 문제를 해결할 수 있었다.
useRef를 실무에서 사용해보니 앞으로도 요긴하게 쓸 일이 많을 거 같아서 이 참에 useRef에 대한 정보를 정리해보고자 한다.
useRef란?
리액트 공식 홈페이지에 나와있는 useRef에 대한 정의는 다음과 같다.
useRef is a React Hook that lets you reference a value that’s not needed for rendering.
즉, 렌더링 하는데 필요하지 않은 값을 참조할 수 있는 리액트 훅이다. useRef는 리액트 컴포넌트에서 주로 DOM 요소나 변수를 참조하는데 사용되는 훅이다.
useRef 사용 예시
import React, { useRef } from 'react';
function MyComponent() {
// DOM 요소 참조 및 초기값 세팅
const inputRef = useRef(null);
// 변수 보존
const countRef = useRef(0);
// DOM 요소의 참조 사용
const handleButtonClick = () => {
console.log(inputRef.current.value);
};
// 변수 값 유지 및 변경
countRef.current += 1;
return (
<div>
// DOM 요소 참조
<input ref={inputRef} type="text" />
<button onClick={handleButtonClick}>Log Input Value</button>
// 변수값 참조
<p>Count: {countRef.current}</p>
</div>
);
}
export default MyComponent;
- useRef의 파라미터
- initialValue: ref 객체의 'current' 속성의 초기값을 설정한다. 어떤 타입의 값이든 할당 할 수 있으며, 초기 렌더링 이후에 이 인자는 무시된다.
- useRef의 return: useRef는 다음과 같은 속성 하나를 가진 객체를 반환한다.
- current: 초기에는 지정한 'initialValue'로 설정된다. 이후에 값을 다른 값으로 설정할 수 있다. 만약 ref 객체를 JSX 노드의 'ref' 속성으로 리액트에 전달한다면, React는 해당 current 속성을 설정한다.
useRef를 사용하면 컴포넌트의 렌더링과 관련된 상태와 상관없이 안정적으로 값을 보존할 수 있다는 장점이 있다. 또한 useRef의 주요 용도와 특징들은 다음과 같다.
1. DOM 요소 참조: useRef를 사용하여 컴포넌트 내의 DOM 요소에 접근할 수 있다. 예를 들어, 특정 DOM 요소의 크기나 위치를 가져오거나 조작할 때 유용하다. useRef를 사용하여 요소를 참조한 후, 해당 요소의 프로퍼티나 메서드를 호출할 수 있다.
2. 변수 보존: useRef를 사용하여 컴포넌트 렌더링 사이에 값이 유지되도록 할 수 있다. 이 값은 렌더링 간에 변하지 않으며, 재랜더링 시에도 초기화되지 않는다. 이것은 컴포넌트의 상태(state)와 달리 렌더링에 영향을 주지 않고 값의 변화를 감지할 때 사용할 수 있다.
3. 객체를 가리키기: useRef는 렌더링 사이에서 변하지 않는 값 뿐만 아니라, 객체의 속성값도 가리키게 할 수 있다. 이를 통해 객체의 속성값을 업데이트하더라도 컴포넌트가 리렌더링되지 않도록 할 수 있다.
4. 컴포넌트 간 통신: useRef를 사용하여 자식 컴포넌트에서 부모 컴포넌트로 데이터를 전달할 수 있다. 값을 변경시키지 않고 데이터를 전달할 때 사용된다.
useRef 사용시 주의사항
- ref.current 속성을 직접 수정할 수 있다. state와 달리, 변경 가능(mutable)하다. 그러나 렌더링에 사용되는 객체(예: state)를 포함하는 경우, 이를 변경하면 안된다.
- state와 달리, ref.current 속성을 바꿀 경우, React는 컴포넌트를 리렌더링 하지 않는다. ref는 일반 자바스크립트 객체이기 때문에 React는 해당 객체가 변경되었을 때, 이를 인지하지 못한다.
- ref가 변경돼도, 리렌더링은 일어나지 않으므로, ref는 화면에 표시할 정보를 저장하기에는 적합하지 않다.
- 초기값 설정을 제외한 렌더링 중에서, 렌더링 도중에 ref.current 값을 읽거나 수정해서는 안된다. 이로 인해 컴포넌트의 동작이 예측할 수 없게 된다.
- ref contents가 다시 생성되는 일은 없게 하라.(특히 비용이 많이 드는 로직의 경우)
- React는 초기 ref 값을 한 번 저장하고, 다음 렌더링에서는 이를 무시한다.
// ❌ 비용이 큰 객체 생성 하는 경우를 초기값으로 설정하지 말 것
function Video() {
const playerRef = useRef(new VideoPlayer());
// ...
}
- 위 코드에서 new VideoPlayer의 결과를 초기 렌더링에서만 사용되지만, 여전히 매 렌더링마다 이 함수가 호출된다. 만약 비용이 큰 객체를 생성하는 경우라면, 이는 큰 낭비가 될 수 있다.
✔ 옳게 된 ref 초기화 방식
function Video() {
const playerRef = useRef(null);
if (playerRef.current === null) {
playerRef.current = new VideoPlayer();
}
// ...
}
- 일반적으로 렌더링 중에 'ref.current'를 쓰거나 읽는 것은 허용되지 않는다. 허나 이 경우에는 항상 같은 결과를 반환하고, 조건문은 초기화 상태에서만 실행되므로, 함수의 기능이 예측 가능하므로 문제가 없다.
[출처]
'FrontEnd > React' 카테고리의 다른 글
ag-grid field 이름에 점(.)이 들어간 경우, valueFormatter의 params.value가 undefined가 되는 현상 (51) | 2023.11.21 |
---|---|
React 애플리케이션에서 모달(Dialog)이 매핑된 배열의 마지막 요소만 표시되는 문제 (0) | 2023.07.24 |
useEffect 정리 (1) | 2023.06.06 |
Material UI Table 사용시 유의사항 (1) | 2023.05.26 |
props란? (0) | 2023.05.26 |