TIL - 20250828

juni·2025년 8월 28일

TIL

목록 보기
108/316

0828 React : Portals, Refs, Side Effects


✅ 1. Side Effects와 Cleanup 함수 (useEffect)

  • Side Effect (부수 효과)란 React 컴포넌트의 주된 역할인 UI 렌더링 외에, 외부 세계와 상호작용하는 모든 작업을 의미합니다.

  • 주요 Side Effects 예시:

    • HTTP 요청 (데이터 fetching)
    • 타이머 설정 (setTimeout, setInterval)
    • DOM 직접 조작
    • 브라우저 API(e.g., localStorage) 사용
  • useEffect Hook: 함수형 컴포넌트에서 이러한 Side Effects를 처리하기 위해 사용하는 Hook입니다. useEffect에 전달된 함수는 컴포넌트가 렌더링된 후에 실행됩니다.

➕ Cleanup 함수 (정리 함수)

  • 개념: useEffect가 반환(return)하는 함수를 Cleanup 함수라고 합니다. 이 함수는 컴포넌트가 DOM에서 제거되기 직전, 또는 useEffect가 다시 실행되기 직전에 호출되어, 이전에 실행했던 Side Effect를 "정리"하는 역할을 합니다.

  • 필요성: 메모리 누수(Memory Leak)를 방지하고, 불필요한 동작을 막기 위해 반드시 필요합니다.

    • 예시: 타이머를 설정한 컴포넌트가 사라졌는데 타이머가 계속 실행되는 경우, 다른 페이지로 이동했는데 이전 페이지의 데이터 요청이 계속되는 경우 등을 방지합니다.
import React, { useState, useEffect } from 'react';

function TimerComponent() {
  const [time, setTime] = useState(0);

  useEffect(() => {
    // Side Effect: 1초마다 time 상태를 1씩 증가시키는 타이머 설정
    const timerId = setInterval(() => {
      setTime(prevTime => prevTime + 1);
      console.log('Timer running...');
    }, 1000);

    // Cleanup 함수: 컴포넌트가 사라질 때 타이머를 제거
    return () => {
      clearInterval(timerId);
      console.log('Timer cleaned up!');
    };
  }, []); // 의존성 배열이 비어있으므로, 컴포넌트가 처음 마운트될 때만 실행

  return <div>Timer: {time}</div>;
}

✅ 2. useRef: DOM 참조와 값의 유지

  • useRef는 두 가지 주요 목적을 위해 사용되는 Hook입니다.
  1. DOM 요소에 대한 직접적인 접근: ref 속성을 통해 특정 JSX 엘리먼트를 직접 참조할 수 있습니다. 이를 통해 해당 DOM 노드의 값을 읽거나, 포커스를 맞추는 등의 작업을 할 수 있습니다.
  2. 리렌더링을 유발하지 않는 값 저장: useState와 달리, useRef로 관리되는 .current 프로퍼티의 값은 변경되어도 컴포넌트가 다시 렌더링되지 않습니다. 컴포넌트의 전체 생명주기 동안 값을 유지해야 할 때 유용합니다.
import React, { useRef } from 'react';

function NameInput() {
  // 1. ref 객체 생성
  const nameInputRef = useRef();

  const handleSubmit = () => {
    // 3. ref.current를 통해 DOM 요소의 값에 직접 접근
    const enteredName = nameInputRef.current.value;
    console.log(enteredName);
  };

  return (
    <>
      {/* 2. ref 속성을 통해 JSX 엘리먼트와 연결 */}
      <input ref={nameInputRef} type="text" />
      <button onClick={handleSubmit}>Submit</button>
    </>
  );
}```

---

### ✅ 3. Portal: 컴포넌트의 DOM 위치 제어

*   **문제점**: 모달(Modal), 툴팁, 알림창과 같은 UI 요소들은 CSS의 `z-index``overflow` 속성 때문에 부모 컴포넌트의 스타일에 갇혀 의도치 않은 UI 깨짐이 발생할 수 있습니다.

*   **해결책 (Portal)**: `ReactDOM.createPortal()`을 사용하면, 컴포넌트의 렌더링 결과를 **물리적으로 다른 DOM 위치**에 삽입할 수 있습니다. 일반적으로 `index.html``root` div와는 별개의 div(e.g., `<div id="overlays"></div>`)에 렌더링합니다.

*   **핵심 특징**: 비록 DOM 위치는 다르지만, **React 컴포넌트 트리 상의 위치는 그대로 유지**됩니다. 따라서 이벤트 버블링이나 Context API 등은 원래의 부모-자식 관계를 따라 정상적으로 동작합니다.

```jsx
// Modal.js
import ReactDOM from 'react-dom';

const ModalOverlay = (props) => {
  return <div>{props.children}</div>;
};

// Modal 컴포넌트의 렌더링 결과를 'overlays' DOM 노드로 보냄
const Modal = (props) => {
  return ReactDOM.createPortal(
    <ModalOverlay>{props.children}</ModalOverlay>,
    document.getElementById('overlays')
  );
};

✅ 4. forwardRefuseImperativeHandle: 부모가 자식 제어하기

  • 문제점: React의 데이터 흐름은 하향식(Props)이 기본 원칙입니다. 하지만 때로는 부모 컴포넌트가 자식 컴포넌트의 DOM 요소에 접근하거나, 자식의 내부 함수를 명령적으로(Imperatively) 호출해야 할 때가 있습니다 (e.g., 부모 버튼 클릭 시 자식 모달을 여는 경우).

  • 해결책: forwardRefuseImperativeHandle을 조합하여 이 문제를 해결합니다.

  1. forwardRef: 부모로부터 받은 ref를 자식 컴포넌트 내부의 특정 DOM 엘리먼트로 "전달"할 수 있게 해주는 고차 컴포넌트(HOC)입니다.
  2. useImperativeHandle: ref를 통해 부모에게 노출될 값을 사용자 정의할 수 있게 해주는 Hook입니다. DOM 노드 자체 대신, open(), close()와 같은 함수를 가진 객체를 노출시킬 수 있습니다.
// 자식 컴포넌트: Modal.js
import { forwardRef, useImperativeHandle, useRef } from 'react';

const Modal = forwardRef(function Modal(props, ref) {
  const dialog = useRef();

  // 부모에게 노출할 함수들을 정의
  useImperativeHandle(ref, () => {
    return {
      open: () => {
        dialog.current.showModal();
      },
      close: () => {
        dialog.current.close();
      }
    };
  });

  return <dialog ref={dialog}>{props.children}</dialog>;
});

// 부모 컴포넌트: App.js
function App() {
  const modalRef = useRef();

  const handleOpenModal = () => {
    // ref를 통해 자식 컴포넌트가 노출한 open 함수를 직접 호출
    modalRef.current.open();
  };

  return (
    <>
      <Modal ref={modalRef}>Modal Content</Modal>
      <button onClick={handleOpenModal}>Open Modal</button>
    </>
  );
}

📌 요약

  • Side EffectuseEffect를 사용하여 관리하며, 불필요한 동작과 메모리 누수를 막기 위해 반드시 Cleanup 함수를 반환해야 합니다.
  • useRef는 DOM 요소에 직접 접근하거나, 리렌더링을 유발하지 않고 값을 유지하고 싶을 때 사용합니다.
  • Portal은 모달과 같이 부모의 CSS 스타일에 구애받지 않고 UI를 렌더링해야 할 때, 컴포넌트를 다른 DOM 위치로 이동시키는 기능입니다.
  • forwardRefuseImperativeHandle을 함께 사용하면, 부모 컴포넌트가 자식 컴포넌트의 함수를 ref를 통해 직접 호출하는 명령적 제어가 가능해집니다.

0개의 댓글