useEffect)Side Effect (부수 효과)란 React 컴포넌트의 주된 역할인 UI 렌더링 외에, 외부 세계와 상호작용하는 모든 작업을 의미합니다.
주요 Side Effects 예시:
setTimeout, setInterval)localStorage) 사용useEffect Hook: 함수형 컴포넌트에서 이러한 Side Effects를 처리하기 위해 사용하는 Hook입니다. useEffect에 전달된 함수는 컴포넌트가 렌더링된 후에 실행됩니다.
개념: 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>;
}
useRef: DOM 참조와 값의 유지useRef는 두 가지 주요 목적을 위해 사용되는 Hook입니다.ref 속성을 통해 특정 JSX 엘리먼트를 직접 참조할 수 있습니다. 이를 통해 해당 DOM 노드의 값을 읽거나, 포커스를 맞추는 등의 작업을 할 수 있습니다.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')
);
};
forwardRef와 useImperativeHandle: 부모가 자식 제어하기문제점: React의 데이터 흐름은 하향식(Props)이 기본 원칙입니다. 하지만 때로는 부모 컴포넌트가 자식 컴포넌트의 DOM 요소에 접근하거나, 자식의 내부 함수를 명령적으로(Imperatively) 호출해야 할 때가 있습니다 (e.g., 부모 버튼 클릭 시 자식 모달을 여는 경우).
해결책: forwardRef와 useImperativeHandle을 조합하여 이 문제를 해결합니다.
forwardRef: 부모로부터 받은 ref를 자식 컴포넌트 내부의 특정 DOM 엘리먼트로 "전달"할 수 있게 해주는 고차 컴포넌트(HOC)입니다.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>
</>
);
}
useEffect를 사용하여 관리하며, 불필요한 동작과 메모리 누수를 막기 위해 반드시 Cleanup 함수를 반환해야 합니다.useRef는 DOM 요소에 직접 접근하거나, 리렌더링을 유발하지 않고 값을 유지하고 싶을 때 사용합니다.forwardRef와 useImperativeHandle을 함께 사용하면, 부모 컴포넌트가 자식 컴포넌트의 함수를 ref를 통해 직접 호출하는 명령적 제어가 가능해집니다.