
2025 / 04 / 03
오후 수업 시간에는 부모 컴포넌트에 노출할 함수나 속성을 지정할 수 있는데, 조금이 아니라 많이 헷갈리는 것 같습니다.. 어떤 식으로 동작하는지는 이해를 한 것 같아서 일단 오늘 작성한 코드와 실습 예제를 바탕으로 자세히 정리해보도록 하겠습니다.
- 부모 컴포넌트가 자식 컴포넌트의 특정 메서드나 속성에 접근할 수 있게 해줍니다.
- 자식 컴포넌트가 부모에게 제공할 메서드나 속성을 제어할 수 있게합니다.
- forwardRef와 함께 사용됩니다.
- 컴포넌트는 자기 자신의 상태를 관리해야합니다.
- 부모 컴포넌트는 자식의 상태를 수정할 수 없습니다.
- 부모가 자식 컴포넌트의 상태나 메서드를 호출해야하는 경우가 발생할 수 있습니다.
- useRef를 사용하면 DOM 요소나 자식 컴포넌트의 인스턴스를 참조할 수 있지만, 기본적으로 자식 컴포넌트의 인스턴스에 직접 접근하는 것은 불가능합니다.
- useImperativeHandle를 사용하여 자식의 기능을 부모에게 노출할 수 있습니다.
1) 부모에서 자식 컴포넌트의 특정 메서드나 상태에 접근할 수 있게 해줍니다.
2) useRef와 forwardRef와 함께 사용됩니다.
3) 자식 컴포넌트에서 직접 메서드를 노출시킬 수 있습니다.
- 컴포넌트 간 통신을 더 명확하게 만들어줍니다.
- 자식 컴포넌트는 부모가 호출할 수 있는 메서드만 노출하므로 의도가 명확해집니다.
- 자식 컴포넌트의 상태 변경을 부모가 직접 제어할 수 있습니다.
- 코드의 재사용성을 높일 수 있습니다.
- 다른 부모 컴포넌트에서도 동일한 자식 컴포넌트를 사용할 수 있습니다.
- 불필요한 메서드 노출은 컴포넌트의 캡슐화를 깨뜨릴 수 있습니다.
- 너무 많은 메서드를 노출시키지 않도록 주의해야합니다.
- 부모 컴포넌트와 자식 컴포넌트 간의 의존성이 생기게 됩니다.
- 너무 자주 사용하면 오히려 코드의 유지보수가 어려워질 수 있습니다.
- 자식 컴포넌트가 내부에서 상태를 직접 관리하는 경우, 부모가 그 상태를 변경할 필요가 없다면 useImperativeHandle을 사용하지 않는 것이 좋습니다.
- useImperativeHandle은 forwardRef와 함께 사용됩니다.
- forwardRef는 부모가 자식 컴포넌트의 ref에 접근할 수 있도록 해줍니다.
- useImperativeHandle은 자식 컴포넌트에서 부모에게 노출할 메서드나 속성을 정의합니다.
1) 부모 컴포넌트는 useRef를 사용하여 자식 컴포넌트의 인스턴스를 참조합니다.
2) 자식 컴포넌트는 forwardRef로 래핑되게 됩니다.
3) 자식 컴포넌트에서 부모에게 노출할 메서드를 정의합니다.
4) 부모 컴포넌트는 자식 컴포넌트의 메서드를 ref.current를 통해 호출할 수 있습니다.
App 컴포넌트
import { useRef } from "react"; import "./App.css"; import "./index.css"; import Modal from "./Modal"; function App() { // modalRef를 선언하여 Modal 컴포넌트를 참조합니다. const modalRef = useRef(null); const handleOpen = () => { console.log(modalRef.current); // modalRef의 현재 상태를 로그에 출력 modalRef.current.open(); // Modal 컴포넌트의 'open' 메서드를 호출 }; const handleClose = () => { // Modal 컴포넌트의 'closeModal' 메서드를 호출 modalRef.current.closeModal(); }; return ( <div> <h1>모달 연습</h1> <button onClick={handleOpen}>모달 열기</button> {/* 모달을 여는 버튼 */} <Modal ref={modalRef} handleClose={handleClose} /> {/* Modal 컴포넌트에 ref를 전달 */} </div> ); } export default App;
Modal 컴포넌트
import { forwardRef, useImperativeHandle, useRef } from "react"; const Modal = forwardRef((props, ref) => { // dialogRef를 사용해 dialog DOM 요소를 참조합니다. const dialogRef = useRef(null); // 부모가 접근할 수 있는 메서드나 속성을 정의합니다. useImperativeHandle(ref, () => { return { open: () => { dialogRef.current.showModal(); // dialog를 열도록 설정 }, closeModal: () => { dialogRef.current.close(); // dialog를 닫도록 설정 }, }; }); return ( <dialog ref={dialogRef}> {/* dialog 요소를 사용해 모달을 구현 */} <h2>모달 창</h2> <p>dialog 태그를 사용하여 모달을 구한할 수 있다.</p> <button onClick={props.handleClose}>모달 닫기</button> </dialog> ); }); export default Modal;
<dialog> 요소를 참조합니다.<dialog> 태그로 모달 창을 구현합니다.
- useImperativeHandle을 사용하여 코드를 작성하였습니다.
- 부모 컴포넌트(App)에서 자식 컴포넌트(Input)의 메서드에 접근하는 구조입니다.
App 컴포넌트
import { useRef } from "react"; import Input from "./Input"; import "./App.css"; export const userData = { name: "", email: "", }; export default function App() { // nameRef를 사용하여 name 입력란에 대한 참조를 생성 const nameRef = useRef(null); // emailRef를 사용하여 email 입력란에 대한 참조를 생성 const emailRef = useRef(null); function handleSaveData() { // nameRef를 통해 name 입력란의 값을 가져옴 userData.name = nameRef.current.getValue(); // emailRef를 통해 email 입력란의 값을 가져옴 userData.email = emailRef.current.getValue(); console.log(userData); // userData 객체를 콘솔에 출력 } return ( <div id="app"> {/* 이름 입력란 */} <Input type="text" label="Your Name" ref={nameRef} /> {/* 이메일 입력란 */} <Input type="email" label="Your E-Mail" ref={emailRef} /> <p id="actions"> {/* 데이터 저장 버튼 */} <button onClick={handleSaveData}>Save Data</button> </p> </div> ); }
- nameRef와 emailRef는 useRef를 사용하여 Input 컴포넌트의 인스턴스를 참조합니다.
- useRef는 특정 DOM 요소나 컴포넌트를 참조하기 위해 사용됩니다.
- Input 컴포넌트에 대한 참조를 저장하여 나중에 값을 가져오는 데 사용합니다.
const nameRef = useRef(null); const emailRef = useRef(null);
- handleSaveData 함수는 버튼 클릭 시 호출됩니다.
- 이 함수는 nameRef와 emailRef를 통해 자식 컴포넌트인 Input의 값을 가져옵니다.
- getValue( ) 메서드를 사용하여 Input 컴포넌트 내부의 입력값을 가져옵니다.
- 가져온 값은 userData 객체에 저장되고, 이후 콘솔에 출력됩니다.
function handleSaveData() { // nameRef를 통해 name 입력란의 값을 가져옴 userData.name = nameRef.current.getValue(); // emailRef를 통해 email 입력란의 값을 가져옴 userData.email = emailRef.current.getValue(); console.log(userData); // userData 객체를 콘솔에 출력 }
Input 컴포넌트
import { forwardRef, useImperativeHandle, useRef } from "react"; const Input = forwardRef(({ label, ...props }, ref) => { const inputRef = useRef(null); // inputRef는 실제 input DOM 요소를 참조 // 부모가 참조할 수 있는 메서드를 정의 useImperativeHandle(ref, () => ({ getValue: () => inputRef.current.value, // input의 값을 반환하는 메서드 })); return ( <p className="control"> <label>{label}</label> {/* input 요소에 ref와 props를 전달 */} <input ref={inputRef} {...props} /> </p> ); }); export default Input;
- forwardRef는 부모 컴포넌트가 자식 컴포넌트에 있는 ref에 접근할 수 있게 해줍니다.
- Input 컴포넌트는 forwardRef로 감싸져 있기 때문에, 부모 컴포넌트에서 Input의 ref를 사용하여 자식 컴포넌트 내부의 ref를 통해 값을 가져올 수 있게 됩니다.
const Input = forwardRef(({ label, ...props }, ref) => { ... });
- inputRef는 useRef로 선언되며, 실제
<input>DOM 요소를 참조합니다.- inputRef를 사용하면 직접 DOM 요소를 접근하여 값을 가져오거나 설정할 수 있습니다.
const inputRef = useRef(null);
- 부모에서 자식 컴포넌트의 ref를 통해 접근할 수 있는 메서드나 값을 정의합니다.
- 부모가 Input 컴포넌트의 ref를 통해 getValue 메서드를 호출할 수 있게 합니다.
- inputRef.current.value를 반환하여 입력값을 부모 컴포넌트로 전달합니다.
- 부모 컴포넌트에서 nameRef.current.getValue( )와 emailRef.current.getValue( )를 호출하면 각 Input 컴포넌트의 입력값을 가져올 수 있게 되는 것입니다.
useImperativeHandle(ref, () => ({ getValue: () => inputRef.current.value, // input의 값을 반환하는 메서드 }));
1. App 컴포넌트
- nameRef와 emailRef를 useRef를 사용하여 각각 Input 컴포넌트에 연결합니다.
2. Input 컴포넌트
- forwardRef로 감싸져 있어, 부모에서 해당 컴포넌트의 ref를 접근할 수 있습니다.
- useImperativeHandle을 사용하여 getValue 메서드를 부모에게 노출합니다.
- 이 메서드는 inputRef.current.value를 반환하여 입력값을 부모에게 전달할 수 있습니다.
3. handleSaveData 함수
- 버튼 클릭 시 호출되는 함수입니다.
- 부모가 참조할 수 있는 메서드 getValue( )를 정의합니다.
4. App 컴포넌트
- nameRef.current.getValue( ), emailRef.current.getValue( )를 통해 값을 가져옵니다.
- userData 객체에 name과 email 값을 저장하고, 콘솔에 출력합니다.
61일차(2) 후기
- 언제 자식 컴포넌트에서 부모 컴포넌트로 메서드를 넘겨줘야하는지 잘 모르겠습니다.
- 계속 사용해보면서 감을 잡아야할 것 같고 forwardRef보다는 괜찮은 것 같습니다.
- 자식 컴포넌트에서 정의한 메서드를 부모의 Ref로 넘겨서, 부모 컴포넌트의 Ref가 해당 메서드를 사용함으로써 자식 컴포넌트의 상태를 관리할 수 있는 것 같습니다.
- 전체적으로는 이해했는데.. 헷갈리는 부분도 있어서 추가 공부를 해야겠습니다..
- 뭐라고 할 순 없는데.. 그냥 어려운 것 같습니다. ξ ・ェ・ ξ