ref
를 직접 할당으로 인한 문제ref
를 사용할 수 없는 이유ref
를 전달하는 방식에 대한 의문점ref
를 사용할 때 주의해야 할 점ref
를 사용하고, 언제 피해야 하는가ref
를 직접 할당으로 인한 문제함수형 컴포넌트는 ref를 사용할 수 없고 ref를 전달하려면 forwardRef를 사용해서 해당 컴포넌트를 감싸야한다.
[문제 코드 예시]
function MyComponent(props) {
return <div>My Component</div>;
}
function ParentComponent() {
const myRef = useRef(null);
return <MyComponent ref={myRef} />;
}
[올바른 코드 예시]
const MyComponent = forwardRef((props,ref) => {
return <div ref={ref}>component</div>;
});
function ParentComponent() {
const myRef = useRef(null);
return <MyComponent ref={myRef} />;
}
ref
를 사용할 수 없는 이유는 뭘까?ref는 DOM 요소나 클래스형 컴포넌트에만 직접적 사용 가능
- 함수형 컴포넌트에서 ref를 직접 사용할 수 없었던 이유는 함수형 컴포넌트는 인스턴스를 가지지 않기 때문에 DOM 요소로 간주 하지 않는다.
- 클래스형 컴포넌트와 달리, 함수형 컴포넌트는 단순히 props를 받아서 UI를 반환하는 순수 함수로 동작, react는 이 함수형 컴포넌트에 대한 인스턴스를 생성하지 않아, ref를 사용할 수 없고, forwardRef를 사용해 DOM노드에 대한 참조를 전달하는 방식으로 해결한다.
- 클래스형 컴포넌트는 생성된 인스턴스를 참조할 수 있기에 ref를 통해 이 인스턴스에 접근하여 내부 메소드나 DOM 노드에 접근할 수 있다.
순수 함수처럼 설계되었기 때문
- props을 받아서 jsx를 반환하는 형태, 입력이 동일하면 항상 동일한 출력을 반환하므로, 상태나 라이프사이클을 관리하지 않아도 되기에, 별도의 인스턴스가 필요가 없다. 모든 상태 관리는 useState와 같은 react 훅이 처리
ref
를 전달하는 방식에 대한 의문점문득 부모에서 자식으로
ref
를 전달하는 게 정말 좋은 방식일까?라는 의문이 생겼다.
솔직히ref
를 통해 자식 컴포넌트의 DOM이나 메소드에 직접 접근하는 건 뭔가 자식 컴포넌트의 자율성을 침해하는 느낌이 든다. 부모가 자식을 완전히 통제하려고 하는 것 같달까? 물론, 특정 상황에서는 필요할 수도 있겠지만, 이런 방식이 컴포넌트 간의 결합도를 높이고 유지보수를 어렵게 만들 수 있다는 생각이 든다.대신, 부모가 자식에게 필요한 동작을 요청하는 방식이 훨씬 더 자연스럽지 않을까 싶다. 예를 들어, 부모가 자식에게 어떤 작업을 요청할 때, 그 작업을 props로 전달된 함수를 통해 자식이 스스로 처리하게 하는 방식 말이다. 이렇게 하면 부모와 자식 컴포넌트가 서로 독립적으로 동작할 수 있어서 결합도도 낮아지고, 코드도 더 명확해진다. 자식 컴포넌트가 부모의 요청을 받아서 그저 필요한 작업만 처리하는 것이니, 서로 간섭 없이 필요한 일을 할 수 있는 셈이다.
보통 부모 컴포넌트에서 자식 컴포넌트로 Ref를 전달해서 사용하는가? NO
부모 컴포넌트가 자식 컴포넌트의 동작을 직접 제어(ref를 통해 조종)를 지양하고 함수나 props를 통해 요청하는 방식이 더 바람직하다.
* 선언적으로 해결될 수 있는 문제
: 어떤 상태나 동작을 외부에서 명령적으로 직접 조작하는 것 대신, 상태의 변화를 통해 자연스럽게 UI가 변화도록하는 방식
const Dialog = forwardRef((props, ref) => {
const [isVisible, setIsVisible] = useState(false);
useImperativeHandle(ref, () => ({
open() {
setIsVisible(true);
},
close() {
setIsVisible(false);
}
}));
return isVisible ? <div className="dialog">Dialog is open</div> : null;
});
function ParentComponent() {
const dialogRef = useRef(null);
return (
<>
<button onClick={() => dialogRef.current.open()}>Open Dialog</button>
<button onClick={() => dialogRef.current.close()}>Close Dialog</button>
<Dialog ref={dialogRef} />
</>
);
}
function Dialog({ isOpen }) {
return isOpen ? <div className="dialog">Dialog is open</div> : null;
}
function ParentComponent() {
const [isDialogOpen, setIsDialogOpen] = useState(false);
return (
<>
<button onClick={() => setIsDialogOpen(true)}>Open Dialog</button>
<button onClick={() => setIsDialogOpen(false)}>Close Dialog</button>
<Dialog isOpen={isDialogOpen} />
</>
);
}
ref
를 사용하고, 언제 피해야 하는가ref
를 사용하기 전에 상태가 어디에 있어야 하는지 고민해야 한다.ref
대신 props나 함수로 요청하는 방식이 더 바람직합니다.