[React] 함수형 컴포넌트에서 ref 사용에 대한 고민

최주희·2024년 9월 9일
1

트러블슈팅

목록 보기
2/2
post-thumbnail

목차

  1. 문제 상황: 함수형 컴포넌트에 ref를 직접 할당으로 인한 문제
  2. 함수형 컴포넌트에서 ref를 사용할 수 없는 이유
  3. 함수형 컴포넌트는 왜 인스턴스를 가지지 않는가?
  4. 부모에서 자식으로 ref를 전달하는 방식에 대한 의문점
  5. 부모-자식 컴포넌트 간의 제어: 직접적인 제어 vs 요청을 통한 제어
  6. React에서 ref를 사용할 때 주의해야 할 점
  7. 명령적 방식 vs 선언적 방식 비교
  8. 결론: 언제 ref를 사용하고, 언제 피해야 하는가

1. 문제 상황: 함수형 컴포넌트에 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} />;
}

2. 함수형 컴포넌트에서 ref를 사용할 수 없는 이유는 뭘까?

ref는 DOM 요소나 클래스형 컴포넌트에만 직접적 사용 가능

  • 함수형 컴포넌트에서 ref를 직접 사용할 수 없었던 이유는 함수형 컴포넌트는 인스턴스를 가지지 않기 때문에 DOM 요소로 간주 하지 않는다.
  • 클래스형 컴포넌트와 달리, 함수형 컴포넌트는 단순히 props를 받아서 UI를 반환하는 순수 함수로 동작, react는 이 함수형 컴포넌트에 대한 인스턴스를 생성하지 않아, ref를 사용할 수 없고, forwardRef를 사용해 DOM노드에 대한 참조를 전달하는 방식으로 해결한다.
  • 클래스형 컴포넌트는 생성된 인스턴스를 참조할 수 있기에 ref를 통해 이 인스턴스에 접근하여 내부 메소드나 DOM 노드에 접근할 수 있다.

3. 함수형 컴포넌트는 왜 인스턴스를 가지지 않는가?

순수 함수처럼 설계되었기 때문

  • props을 받아서 jsx를 반환하는 형태, 입력이 동일하면 항상 동일한 출력을 반환하므로, 상태나 라이프사이클을 관리하지 않아도 되기에, 별도의 인스턴스가 필요가 없다. 모든 상태 관리는 useState와 같은 react 훅이 처리

4. 부모에서 자식으로 ref를 전달하는 방식에 대한 의문점

문득 부모에서 자식으로 ref를 전달하는 게 정말 좋은 방식일까?라는 의문이 생겼다.
솔직히 ref를 통해 자식 컴포넌트의 DOM이나 메소드에 직접 접근하는 건 뭔가 자식 컴포넌트의 자율성을 침해하는 느낌이 든다. 부모가 자식을 완전히 통제하려고 하는 것 같달까? 물론, 특정 상황에서는 필요할 수도 있겠지만, 이런 방식이 컴포넌트 간의 결합도를 높이고 유지보수를 어렵게 만들 수 있다는 생각이 든다.

대신, 부모가 자식에게 필요한 동작을 요청하는 방식이 훨씬 더 자연스럽지 않을까 싶다. 예를 들어, 부모가 자식에게 어떤 작업을 요청할 때, 그 작업을 props로 전달된 함수를 통해 자식이 스스로 처리하게 하는 방식 말이다. 이렇게 하면 부모와 자식 컴포넌트가 서로 독립적으로 동작할 수 있어서 결합도도 낮아지고, 코드도 더 명확해진다. 자식 컴포넌트가 부모의 요청을 받아서 그저 필요한 작업만 처리하는 것이니, 서로 간섭 없이 필요한 일을 할 수 있는 셈이다.

5. 부모-자식 컴포넌트 간의 제어: 직접적인 제어 vs 요청을 통한 제어

보통 부모 컴포넌트에서 자식 컴포넌트로 Ref를 전달해서 사용하는가? NO

부모 컴포넌트가 자식 컴포넌트의 동작을 직접 제어(ref를 통해 조종)를 지양하고 함수나 props를 통해 요청하는 방식이 더 바람직하다.

핵심개념 : 직접적인 제어 (Ref) vs 요청을 통한 제어 (Props, 함수)

  1. 부모가 자식을 직접 제어하는 방식(Ref)
    1. 부모가 자식으로 ref를 전달해서 자식 컴포넌트 내부의 DOM요소나 특정 메소드에 직접 접근하는 것은 부모가 자식을 직접 조종하는 것과 같다.
    2. 자식 컴포넌트의 독립성을 깨뜨리고 컴포넌트 간의 의존성을 꼬이게 만든다.
  2. 요청을 통한 제어 (props, 함수)
    1. 부모가 자식에게 특정 동작을 요청하고 , 자식이 그 요청에 따라 동작을 수행하는 방식
    2. 부모는 자식의 내부 구현에 의존하지 않고 필요한 동작만 요청 가능

6. React에서 Ref를 사용할 때, 주의해야할 점

  1. 선언적으로 해결될 수 있는 문제애 대해서는 ref 사용을 지양해야한다.
    • ex) Dialog 컴포넌트를 만들 때, open(), close()를 두는 대신 isOpen이라는 Props를 전달하여 만들도록 해야한다.

* 선언적으로 해결될 수 있는 문제
: 어떤 상태나 동작을 외부에서 명령적으로 직접 조작하는 것 대신, 상태의 변화를 통해 자연스럽게 UI가 변화도록하는 방식

7. 명령적 방식 vs 선언적 방식 비교

명령적 방식 (Ref 사용)

  • 명령적으로 dialog를 열고 닫기 위해 ref로 open()과 close() 같은 메소드 호출 가능
  • ref로 자식 컴포넌트의 open(), close() 메소드를 직접 호출해 dialog를 열고 닫는다 이 방식은 상태를 직접 명령하는 것이다.
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} />
    </>
  );
}

선언적 방식 (props로 해결)

  • 선언적으로 해결하는 방식에서는 Dialog 컴포넌트가 상태에 따라 스스로 열리고 닫힌다.
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} />
    </>
  );
}

8. 결론: 언제 ref를 사용하고, 언제 피해야 하는가

  • ref를 사용하기 전에 상태가 어디에 있어야 하는지 고민해야 한다.
    선언적으로 상태를 관리할 수 있다면 ref 대신 props나 함수로 요청하는 방식이 더 바람직합니다.
  • 부모가 자식을 직접 제어하기보다는 요청을 통해 자식이 스스로 동작하게 하는 것이 컴포넌트의 독립성을 유지하고, 코드의 유지보수성을 높이는 데 도움이 됩니다.
profile
큰 목표보단 꾸준한 습관 만들기

0개의 댓글