React (Portal과 useRef)

Jeonghun·2023년 5월 25일
3

React

목록 보기
6/21


React의 Portal과 useRef 이해하기

- Portal

React Portal은 컴포넌트를 일반적인 컴포넌트 트리 외부에 렌더링하게 해주는 기능이다. 다시 말해서, 컴포넌트를 부모 컴포넌트의 외부, 심지어는 DOM 트리 완전히 다른 부분에 삽입할 수 있도록 해준다.

🤔 그럼 언제 Portal을 사용하면 좋을까?
포탈 기능은 주로 모달, 툴팁, 팝업 등의 UI 구성 요소를 만들 때 사용한다. 이러한 요소들은 DOM 트리에서 독립적으로 동작하면서, 컴포넌트의 배치에 있어 유연성이 요구되는 경우가 많기 때문이다.

📌 Portal 예시

<!-- index.html -->
<body>
  <div id="root"></div>
  <!-- div의 id를 포탈 루트로 설정 -->
  <div id="portal-root"></div>
</body>
// App.jsx
import React from 'react';
import ReactDOM from 'react-dom'; // 포탈을 사용하기 위해 react-dom import

function App() {
  // ReactDOM.createPortal()을 통해 포탈을 생성할 수 있음
  return ReactDOM.createPortal(
    <h1>Hello from Portal!</h1>,
    // getElementById로 index.html에서 지정한 'portal-root' id를 가진 div 요소를 포탈로 지정
    document.getElementById('portal-root')
  );
}

export default App;

위의 예제 코드에서 ReactDOM.createPortal()을 사용해 'Hello from Portal!'라는 메시지를 '#portal-root' DOM 노드에 직접 렌더링하고 있다. 이를 통해, 'App' 컴포넌트가 속한 '#root' DOM 노드 밖에 있는 '#portal-root'에 직접 컴포넌트를 렌더링할 수 있다.

🤔 뭐가 좋은지 잘 모르겠는데..?
React DOM의 Portal 기능을 사용하면, 다음과 같은 장점이 있다.

  • UI 유연성: Portal을 사용하면, DOM 트리의 아무 곳에서나 컴포넌트를 렌더링할 수 있. 이는 모달, 툴팁, 팝업과 같이 페이지의 특정 부분에서 독립적으로 렌더링되어야 하는 UI 요소를 만드는 데 유용하게 적용된다.

  • CSS 스타일링 용이성: Portal을 사용하면, 부모 컴포넌트의 CSS 스타일에 영향을 받지 않고 독립적으로 스타일링할 수 있다. 이는 복잡한 CSS 상속을 방지하고, UI 요소의 독립성을 유지하는 데 도움이된다.

  • 코드 가독성: Portal을 사용하면, DOM 트리 구조와 상관없이 UI 요소를 렌더링할 수 있다. 이를 통해, 코드의 가독성을 높이고, UI 구조를 더 명확하게 이해할 수 있다.

모달과 같은 UI 요소를 만들 때 portal 기능을 적절히 사용해보자!

- state에 이은 또 다른 hook, useRef

React의 useRef 훅은 컴포넌트에서 변경 가능한 값을 안전하게 저장하고 참조할 수 있게 해주는 기능이다. 이 값은 '컴포넌트의 렌더링과는 독립적이며, 해당 값이 변하더라도 컴포넌트가 다시 렌더링되지 않는다.' 는 특징을 가진다.

📌 useRef 사용 예시

useRef는 기본적으로 아래와 같이 사용한다.

import React, { useRef } from 'react'; // useRef import

const refContainer = useRef(initialValue);

위 코드에서 initialValue는 생성된 참조의 초기값으로 설정된다. 이 초기값은 .current 프로퍼티를 통해 접근할 수 있다.

- useRef를 통해 DOM 노드에 접근하기

가장 일반적인 useRef의 사용 예시 중 하나는 DOM 노드에 접근하는 것이다. 이를 통해 직접적인 DOM 조작, 포커스 관리, 특정 DOM 이벤트의 핸들링 등을 수행할 수 있다.

다음 예제 코드는 useRef를 사용하여 input 요소에 접근하고, 포커싱하는 예시이다.

import React, { useRef } from 'react';

function TextInputWithFocusButton() {
  const inputEl = useRef(null); // inputEl은 input 요소에 대한 참조를 저장하는 데 사용

  const onButtonClick = () => {
    // `current`는 현재 마운트된 text input 요소를 가리킴
    inputEl.current.focus(); // current 프로퍼티를 사용하여 실제 input 요소에 접근하고 focus 메서드를 호출
  };

  return (
    <>
      <input ref={inputEl} type="text" /> {/* input 요소에 대한 참조 설정 */}
      <button onClick={onButtonClick}>Focus the input</button> {/* 버튼 클릭 시 onButtonClick 핸들러가 호출되어 input 요소에 포커스 */}
    </>
  );
}

위 예제는 React에서 DOM 요소에 직접 접근하는 방법을 보여준다. useRef를 사용하여 참조(ref)를 생성하고, 이 참조를 DOM 요소의 ref 속성에 할당한다. 이렇게 하면 해당 참조의 current 프로퍼티를 통해 실제 DOM 요소에 접근할 수 있게 되는 것이다.

다음으로 useRef를 통해 컴포넌트 렌더링 사이에 데이터를 유지하는 예시 코드를 살펴보자.

import React, { useRef, useEffect } from 'react';

function Component() {
  const renderCount = useRef(0); // renderCount는 렌더링 횟수를 저장하는 데 사용

  useEffect(() => {
    renderCount.current = renderCount.current + 1; // 컴포넌트가 렌더링될 때마다 renderCount의 값을 1 증가시킴
  });

  return (
    <h1>I have rendered {renderCount.current} times!</h1> // 렌더링 횟수를 화면에 출력함
  );
}

위 예제는 useRef가 불변(immutable) 참조를 생성하며, 이 참조의 current 프로퍼티만 변경 가능하다는 특성을 보여준다. 이 특성 덕분에 useRef는 렌더링 사이클에 무관하게 데이터를 유지할 수 있다. 이 예시에서 렌더링 횟수를 저장하는 renderCount는 값이 변경되어도 컴포넌트를 다시 렌더링하지 않는다. 이는 renderCount가 상태가 아닌 참조이기 때문이다.

🤔 너무 복잡해! 이걸 쓰면 뭐가 좋은거야?
useRef를 사용하면 다음과 같은 장점을 가질 수 있다.

  • DOM 요소 접근: useRef를 사용하면, DOM 요소에 직접 접근할 수 있다. 이를 통해 DOM 요소의 속성을 읽거나 변경하거나, DOM API를 사용하는 등의 작업을 할 수 있다.

  • 렌더링과 독립적인 값 관리: useRef를 사용하면, 컴포넌트 렌더링과 상관 없이 변경 가능한 값을 안전하게 저장하고 참조할 수 있다. 이는 시간 경과에 따라 변경되지만 렌더링에는 영향을 미치지 않는 값의 관리에 유용하다. 예를 들어, 애니메이션, setTimeout, setInterval 등의 작업에서 이전 값을 참조해야 하는 경우에 useRef를 활용해볼 수 있다.

  • 참조의 투명성: useRef는 자신이 참조하는 값을 '.current' 속성으로 노출하게 된다. 이는 참조 대상의 변경 사항이 외부로부터 투명하게 관찰될 수 있음을 의미한다.

🤔 useState와는 어떤 점이 다를까?
useState와 useRef는 둘 다 컴포넌트에서 값을 관리하는데 사용되지만, 값의 변경이 컴포넌트의 렌더링에 미치는 영향이 다르다. useState는 상태가 변경되면 컴포넌트가 다시 렌더링되지만, useRef는 값의 변경이 컴포넌트의 렌더링에 영향을 미치지 않는다.


profile
안녕하세요, 프론트엔드 개발자 임정훈입니다.

0개의 댓글