[React] 함수형 컴포넌트에서 ref
사용에 대한 고민
회사의 현업 개발자 제롬님께 모르는 것을 쏟아냈다. 어느 정도 갈피를 잡을 수 있었다. 그동안 아무것도 모르고 사용했던 ref의 용도도 제대로 이해하게 되었고, 언제 어떻게 사용하는 게 좋은지도 알게 되었다. (Merci beaucoup.😃)
[IndustryForm]
<DropDownBox isWholesaler={isWholesaler}>
<Dropdown
name='sectorId'
options={industryData}
placeholder='업종 카테고리를 선택해주세요'
/>
</DropDownBox>
[DropDown]
const Dropdown = forwardRef<HTMLDivElement, Props>(
({ options, placeholder, name }, ref) => {
const industryRef = useRef<HTMLDivElement>(null);
useClickOutside(industryRef, () => setIsOpen(false));
return (
<DropdownContainer ref={ref}>
{/* Dropdown content */}
</DropdownContainer>
);
}
);
export default Dropdown;
forwardRef는 드롭다운을 열고 닫는 기능을 제어하려고 사용했다. 하지만.. 이 코드는 불필요하고, 단지 공간만 차지 할 뿐이었다.
forwardRef를 잘 모르고 썼었다.
나는 dropdown 외부를 클릭하기 위해서는 부모에 ref를 전달해서 부모가 직접 드롭다운을 열고 닫기를 제어해야하는 줄 알았다.
정말 잘못된 생각이었다.
내 코드에서는 굳이 부모 컴포넌트가 dropdown 컴포넌트의 내부 DOM 요소에 직접 접근하거나 조작할 필요가 없기 때문에 쓰지 않아도 된다.
이미 Dropdown 내부에서 상태를 관리하고 있기 때문에 부모는 신경 쓸 필요가 없고 UI는 상태에 따라 동적으로 렌더링하고 있기 때문이다.
function Dropdown({ options, placeholder, name }: Props) {
const industryRef = useRef<HTMLDivElement>(null);
useClickOutside(industryRef, () => setIsOpen(false));
return (
<DropdownContainer ref={industryRef}>
{/* Dropdown content */}
</DropdownContainer>
);
}
export default Dropdown;
이렇게 자식 컴포넌트를 조작하는 로직이 필요하다.
function parentComponent() {
const dropdownRef = useRef(null);
const openDropdown = () => {
dropdownRef.current.style.display = 'block'; // 부모가 직접 DOM에 접근해 열고 닫기 제어
}
return (
<div>
<button onClick={openDropDown}>모달 오픈!</button>
<Dropdown ref={dropdownRef}/>
</div>
)
}
const Dropdown = forwardRef<HTMLDivElement, Props>(
({ options, placeholder, name }, ref) => {
const industryRef = useRef<HTMLDivElement>(null);
useClickOutside(industryRef, () => setIsOpen(false));
return (
<DropdownContainer ref={ref}>
{/* Dropdown content */}
</DropdownContainer>
);
}
);
ref 계속 사용해도 되는 줄 알았다.
ref는 특정 DOM 요소에 대한 직접적인 참조를 생성하는데, 한 번에 한 개의 요소만 참조 할 수 있다.
따라서 PlaceRef를 여러 요소에 사용하면 React는 ref.current에 마지막으로 할당된 요소만 기억하게 된다.
<InputWrapper ref={placeRef}> // 여기서 placeRef에 할당됨
...
{isPostcodeVisible && (
<div className='post-modal' ref={placeRef}> // 다시 placeRef에 할당되며, 이전 참조는 사라짐
<DaumPostcode onComplete={handleSelect} />
</div>
)}
</InputWrapper>
InputWrapper도 모달 개념이라고 판단해 닫아줘야한다고 생각했고, 주소검색을 위해 열리는 post-modal도 당연히 닫히는 대상이라 둘 다 추가 했다. 생각해보면 실제로 닫히는 대상은 post-modal뿐이다. InputWrapper는 모달이 포함된 전체 영역일뿐.
forwardRef는 자주 사용하지 않는다. 필요할 때만 신중하게 사용하는 것이 중요하고, 대부분 컴포넌트 내부에서 상태를 관리하는 방식이 더 적절하다.
forwardRef를 감싸지 않아도 props로 ref를 전달 받을 수 있게 됐다.
// 19 이후
function App() {
const buttonRef = useRef();
const onButtonClick = () => console.log(buttonRef.current);
return (
<Button ref={buttonRef} onClick={onButtonClick}>
클릭
</Button>
);
}
const Button = ({ ref, ...props }) => {
return <button ref={ref} {...props} />;
};