JS를 사용 할 때, 특정 DOM을 선택해야 하는 상황에서 getElementById, querySelector 같은 DOM Selctor 함수를 사용해서 DOM을 선택합니다.
이럴때 리액트에서는 ref를 사용합니다.
함수형 컴포넌트에서 ref 를 사용 할 때에는 useRef 라는 Hook 함수를 사용합니다.
UseRef
1. DOM 접근
useRef는 React 애플리케이션에서 DOM 요소에 직접적으로 접근하기 위한 기능입니다. Refs를 사용하면 React 컴포넌트에서 DOM 요소에 접근하거나 조작할 수 있습니다. 주로 입력 필드의 포커스 설정, 애니메이션 트리거, 외부 라이브러리와의 통합 등의 경우에 활용됩니다.
여기서 Ref는 reference, 즉 참조를 뜻합니다.
간단한 예제로 어떻게 사용하는지 살펴보겠습니다.
import { useState, useRef } from "react";
export default function Player() {
const playerName = useRef();
const [enteredPlayerName, setEnteredPlayerName] = useState("");
function handleClick() {
setEnteredPlayerName(playerName.current.value);
}
return (
<section id="player">
<h2>Welcome {enteredPlayerName ?? "unknown entity"}</h2>
<p>
<input ref={playerName} type="text" />
<button onClick={handleClick}>Set Name</button>
</p>
</section>
);
}
위 예제는 input필드에 사용자가 이름을 입력하고 Set Name 버튼을 누르면, h2태그에 해당 값을 출력하는 예제입니다.
이 예제에서는 useRef를 사용해 ref를 생성하고, 해당 ref를 input 요소에 할당합니다.
이후, 버튼을 클릭시 handleClcik 함수를 통해 해당 ref를 사용해 input에 입력된 값을 enteredPlayerName 상태 설정합니다.
2. 저장공간
useRef() Hook은 DOM ref만을 위한 것이 아닙니다. 본질적으로 useRef는 .current 프로퍼티에 변경 가능한 값을 담고 있는 “상자”와 같습니다.
useRef는 상자와 같으므로 useState처럼 컴포넌트 내의 변수 값을 조회, 수정하는 방법으로도 사용할 수 있습니다.
리액트 컴포넌트는 State가 변경될 때 마다 다시 렌더링 되면서 컴포넌트 내부의 변수들이 초기화 됩니다. 이것은 모든 로직이 다시 실행되는 것을 의미하며, 이러한 원하지 않는 렌더링 때문에 곤란할 때가 있습니다.
이 떄, State 대신 Ref에 값을 저장 할 수 있습니다.
Ref는 안에 있는 값을 변경해도 컴포넌트가 다시 렌더링 되지 않습니다
즉, 렌더링 전반에 걸쳐 지속되어야 하지만 컴포넌트의 UI에 영향을 주지 않는 값이 있다면 useRef를 사용하는 것이 좋습니다.
forwardRef
forwardRef는 부모 컴포넌트에서 자식 컴포넌트 안의 DOM element에 접근하고 싶을 때 사용합니다.
React 컴포넌트에서 ref prop을 사용하려면 forwardRef라는 함수를 사용해야 합니다.
컴포넌트를 forwardRef()라는 함수로 감싸주면, 해당 컴포넌트는 함수는 두 번째 매개 변수를 갖게 되는데, 이를 통해 외부에서 ref prop을 넘길 수 있습니다.
아래의 사용 예시를 보겠습니다.
해당 코드는 타이머의 시간이 지나면 다이얼로그를 렌더링하는 코드입니다. 설명과 관련된 코드만 첨부했습니다.
1. 부모 컴포넌트에서 useRef 를 선언하고, 자식 컴포넌트에 전달합니다.
import { useRef, useState } from 'react';
import ResultModal from './ResultModal';
export default function TimerChallenge({ title, targetTime }) {
const dialog = useRef();
...
return (
<>
<ResultModal ref={dialog} targetTime={targetTime} result='lost' />
...
</>
);
}
2. 자식 컴포넌트를 forwardRef()로 감싸고 해당 함수의 두번째 매개변수를 통해 ref를 받습니다.
부모 컴포넌트에서 사용할 함수를 useImperativeHandle()로 감쌉니다.
import { forwardRef, useImperativeHandle, useRef } from 'react';
const ResultModal = forwardRef(function ResultModal({ result, targetTime }, ref) {
const dialog = useRef();
useImperativeHandle(ref, () => {
// 이 반환값이 부모 컴포넌트에서 useRef() 호출시 반환 된 객체의 current에 바인딩 될 값
return {
open() {
dialog.current.showModal();
},
};
});
return (
<dialog ref={dialog} className='result-modal'>
...
<form method='dialog'>
<button>Close</button>
</form>
</dialog>
);
});
export default ResultModal;
부모 컴포넌트에서 current 속성을 통해 함수를 사용합니다.
import { useRef, useState } from 'react';
import ResultModal from './ResultModal';
export default function TimerChallenge({ title, targetTime }) {
const dialog = useRef();
function handleStart() {
timer.current = setTimeout(() => {
setTimerExpired(true);
dialog.current.open();
}, targetTime * 1000);
setTimerStarted(true);
}
...
return (
<>
<ResultModal ref={dialog} targetTime={targetTime} result='lost' />
...
</>
);
}
'React' 카테고리의 다른 글
react 양방향 바인딩 (2way data binding) (1) | 2024.01.05 |
---|---|
React 이미지 저장 public vs assets (0) | 2024.01.04 |