
본 포스트는 Udemy 리액트 완벽가이드 2024 를 듣고 정리한 내용입니다.
목차 🌳
1️⃣ Refs(참조)를 통해 DOM elements에 접근하기 / Refs로 state가 아닌 값 제어
2️⃣ Portal이란?
useRef 왜 필요한데요? 🥸
불필요한 재랜더링을 막아줌 + DOM 요소에 접근이 용이함
아래의 예시는, 유저의 이름을 입력 받아 클릭하면 h2 태그에 입력한 값을 출력하는 간단한 UI이다. useRef를 사용하지 않은 코드와, 사용한 코드를 비교해보며 useRef에 대해서 알아보자

사용 이전코드
export default function Player() {
const [inputPlayerName, setinputIlayerName] = useState('player 1');
const [submitName, setSubmitName] = useState(false);
function handleClick() {
setSubmitName(true);
}
function handleChange(e) {
setSubmitName(false);
setinputIlayerName(e.target.value);
}
return (
<section id="player">
<h2>Welcome {submitName ? inputPlayerName : 'unknown entity'}</h2>
<p>
<input onChange={handleChange} value={inputPlayerName} type="text" />
<button onClick={handleClick}>Set Name</button>
</p>
</section>
);
}
위에 코드는 사람의 이름을 입력할 때마다, 재랜더링이 일어남 (useState 값을 변경함수로 바꿔서)
근데 사실 뭐 검색 기능도 아니고, 그럴 필요가 전혀 없는 이상황에서 우린 Ref 를 사용해볼 수 있음
import { useState, useRef } from 'react';
export default function Player() {
const [inputPlayerName, setinputIlayerName] = useState(null);
const userNameRef = useRef(); // useRef() 로 ref 값 선언
function handleClick() {
setinputIlayerName(userNameRef.current.value); //
userNameRef.current.value = '';
}
return (
<section id="player">
<h2>Welcome {inputPlayerName ?? 'unknown entity'}</h2>
<p>
<input ref={userNameRef} type="text" />
<button onClick={handleClick}>Set Name</button>
</p>
</section>
);
}
잠깐 useref 가 정확히 뭐고, 어떻게 사용하는건데? 🤨
useRef 가 정확히 뭔데? 🤨
useState와 마찬가지로, 리액트 Hook의 한종류이며!UseRef를 통해 반환되는 객체값을 통해, 내장되어 있는current라는 속성에 접근하면, ref로 연결한 html의 DOM node 값이 저장된다! 또한, 특정 DOM 요소에 접근이 가능해지면, "불필요한 재랜더링 없이" 값의 참조가 가능하다.
useState 와의 차이점 (<-> useRef)
useRef: 참조값이 변경되더라도, 컴포넌트 함수가 재실행되지 않는다는 것 (재랜더링 ❌)
-> 값이 변화되지만, 화면 너머의 변경에 대해서 즉각적인 UI의 영향이 필요없을 떄
-> DOM 요소에 직접적인 접근이 필요할때 (특정값 읽기나 특정 API 이용시 )
useState: 상태가 상태변경 함수로 변경될 경우, 컴포넌트 함수가 재실행됨 (재랜더링 ⭕️)
-> 값의 변화로 인해, UI에 변경사항이 반영되어야 하는 경우
useRef 그래서 어떻게 쓰나 🤨
1️⃣ useRef import 해오기
import { useRef } from 'react';
2️⃣ 컴포넌트 최상위 레벨에서 useRef 호출하여, 하나 이상의 변수 선언
const inputRef = useRef(initialValue);(변수명 자유)
3️⃣ ref 속성으로 조작하고 싶은 DOM 노드의 JSX에, ref 객체를 전달해주기
<input ref={inputRef} />
4️⃣ ref 객체의 current 프로퍼티를 통해, DOM 노드에 접근 할 수 있음
inputRef.current.value➡️ input의 value 에 접근 가능!!!
단, 참조를 하는 목적이 페이지의 모든 종류의 값들을 읽고, 조정하기 위해서가 되어서는 안됨
=> 왜냐하면, DOM의 상호작용은 리액트가 하는게 맞기 때문에 useRef가 주가 되면 안된다는 말씀
근데 이제, 한 컴포넌트에서 다른 컴포넌트도 ref를 전달해주고 싶을때가 있을 것이다. 이를테면, 모달을 띄우고 싶을 때와 같은 순간 말이다.
타이머가 초과한 경우, 게임에 대한 결과값을 나타내는 모달을 나타나게 하는 코드는 다음과 같다.
게임박스 컴포넌트 중, 모달 관련
{timerExpired && <ResultModal ref={dialogRef} targetTime={targetTime} result="lost" />}
모달 컴포넌트
export default function ResultModal({ ref, result, targetTime }) {
return (
<dialog ref={ref} className="result-modal">
<h2>you {result}</h2>
<p>
The target time was <strong>{targetTime} seconds.</strong>
</p>
<p>
You stopped the timer with <strong>X seconds left.</strong>
</p>
<form method="dialog">
<button>Close</button>
</form>
</dialog>
);
}
이렇게 props 값으로 ref를 전달하는 걸 생각해볼 수 있겠는데, 이러면 오류가 난다,

대충 ref는 프롭이 아니란 소리
그러면 우리는 ref을 다른 컴포넌트로 전달해줄 순 없는걸까..~?
당근 되지롱
⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️
forwardRef 쓰기 ✨
forwardRef()란 컴포넌트에서refprop을 넘겨, 그 내부에 있는 HTML 엘리먼트에 접근을 가능하게 해주는 함수!
ref를 prop으로 넘겨주고 싶다? 그러면 forwardRef 쓰면됨
⚙️사용방법
1️⃣ prop을 받을 컴포넌트에서,import { forwardRef } from 'react';해오기
2️⃣ 컴포넌트를 forwardRef() 안에 넣어주기const ResultModal = forwardRef(function ResultModal({ result, targetTime }, ref) { return ( <dialog ref={ref} className="result-modal"> <h2>you {result}</h2> <p> The target time was <strong>{targetTime} seconds.</strong> </p> </dialog> ); });3️⃣ 단, 위와 같이 컴포넌트에서
props은2번째 인자에 ref를 받고, 1번째 인자에서는 나머지 것들을 받아주면 된다.

야 포털 너는 또 뭐냐 🕳️ ?
(바로결론): 하위 요소를DOM의 다른 부분으로 렌더링시켜주는 함수(한마디로 걍 계층 상관없이 숑 원하는 곳에서 랜더링 시켜주는 순간이동)
리액트는부모 컴포넌트랜더링 ->자식 컴포넌트랜더링 되는 식으로Tree 구조를 띄고 있음. 이런Tree 계층 구조에서 모달 컴포넌트가 하위요소로 박혀있다보면, 부모 컴포넌트에 의해 영향을 받기도 하고 (z-indexoverflow: hidden), 덜 직관적이다. 그래서 보통은body바로 아래에 위치시키곤 한다. 이때,portals를 사용하면 부모 컴포넌트의 외부 DOM 으로 컴포넌트를 랜더링 시킬 수 있다.
👀 사용방법
1️⃣ 순간이동 시키고 싶은 (portals로 상위에 올리고 싶은) 컴포넌트 파일에서,createPortalimport
->import { createPortal } from 'react-dom';
2️⃣createPortal(child, container)
child에는 문자열 혹은 JSX 조각과 같이 랜더링 가능한 하위 요소를 담아주고, (줄 거)
container에는DOM요소를 넣어준다.document.getElementById('id')(받을 장소)