Callback refs란?

  1. 조건부 렌더링시 dom 조작
  2. 렌더링 지연시 dom 조작

우선 제가 해결해야했던 경우는 위 두 가지입니다.
보통 우리가 ref를 조작해서 어떤 dom에 focus 한다고 가정했을 때, 돔이 렌더링 되었을 때 focus합니다.

일반적인 ref 사용법

function Component() {
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    inputRef.current?.focus();
  }, []);

  return <input ref={inputRef} />
}

상단 코드가 일반적인 경우가 되겠죠.
해당 코드는 정상 작동하고 아무런 문제가 없습니다.

아래 코드를 봐주세요.

조건부 렌더링시 ref 조작 실패

function Component() {
  const [show, setShow] = useState(false);
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    inputRef.current?.focus();
  }, []);

  const handleClickShowHideToggleButton = () => {
    setShow(prev => !prev);
  };

  return (
    <>
      <button onClick={handleClickShowHideToggleButton}>show/hide</button>
      {show && <input ref={inputRef} />}
    </>
  );
}

제가 위에서 말씀드린 두 가지 경우 중에 1번에 속하는 상황입니다.

  1. 조건부 렌더링시 dom 조작
  2. 렌더링 지연시 dom 조작

해당 코드는 정상작동하지않습니다.

해당 코드 결과는 어떻게 될까요? 컴포넌트가 렌더링된 후, focus가 정상적으로 동작하지 않을 겁니다.
useEffect가 처음 실행될 때 ref가 존재한다는 가정하에 적절한 타이밍에 실행되기 때문입니다. 해당 코드는 show 상태를 기준으로 조건부 렌더링이 되고 있고, 현재 show 상태는 false이므로 input이 렌더링 되지않을 것이며, 따라서 렌더링이 되는 시점엔 ref.current가 null이 됩니다. 따라서 focus가 되지않습니다.

이를 해결한 Callback Refs 적용 코드를 살펴보겠습니다.

Callback refs를 적용한 조건부 렌더링 코드

function Component() {
  const [show, setShow] = useState(false);

  const handleClickShowHideToggleButton = () => {
    setShow(prev => !prev);
  };
  
  const inputRef = useCallback((node) => {
    node?.focus();
  }, [])

  return (
    <>
      <button onClick={handleClickShowHideToggleButton}>show/hide</button>
      {show && <input ref={inputRef} />}
    </>
  );
}

useEffect 부분을 지웠고, inputRef 함수를 새로 생성했습니다.
이제 React 생명주기에 바인드 된 것이 아닌 DOM 생명주기에 바인드 되었으므로, 대부분의 상황에서 문제없이 사용할 수 있습니다. 엄격모드에서도 한 번 실행됩니다.

근데 inputRef가 함수라는 것을 확인할 수 있습니다.
dom의 ref는 사실 함수입니다.

ref={(node) => {
  ref.current = node;
}}

ref는 node의 렌더링 이후에 실행되고 있습니다.

Callback refs인 이유

참조불변성을 이용해 함수를 재생성하지 않음으로서 단 한번만 포커스하려는 의도를 반영할 수 있습니다. useCallback을 사용해 함수의 재생성을 막아주지않으면 리렌더링시 계속 focus 하게 됩니다. 그래서 Callback을 사용해 메모이징 해주어야합니다.

useRef로 ref를 생성해 Dom에게 전달하는데, 이 ref가 변하는 값인가요? 아닙니다. 같은 이치로 참조불변성을 이용해 함수의 재생성을 막아주는 것입니다.

결론

조건부 렌더링과 렌더링 지연등의 환경에서 dom의 조작을 원활하게 하고싶다면 Callback Refs를 사용하는 것이 좋습니다. 분명 메모이제이션은 비용이 드는 작업이지만, 그만한 가치가 있다고 생각됩니다.

읽어주셔서 감사합니다.

profile
DX를 사랑하는 개발자

0개의 댓글

Powered by GraphCDN, the GraphQL CDN