Typescript에서 useRef Error

BORA·2022년 4월 12일
2
post-custom-banner

에러 내용

개체가 'null'인 것 같습니다.

문제 코드

interface IProps {
  targetCamp: ICampDetail;
}

const ContentsSection = ({ targetCamp }: IProps) => {
  const containerRef = useRef<HTMLElement>(null);
  const imgRef = useRef<HTMLElement>(null);
  const [height, setHeight] = useState<number>(0);

  const onloadImages = useCallback(() => {
    const imgs = imgRef.current.querySelectorAll('img');
    for (let i = 0; i < imgs.length; i++) {
      imgs[i].onload = () => {
        if (i === imgs.length - 1) getHeight();
      };
    }
  }, []);

  const getHeight = () => {
    setHeight(containerRef.current.clientHeight);
  };

  useEffect(() => {
    targetCamp && onloadImages();
  }, [onloadImages, targetCamp]);

  return (
    <Container ref={containerRef}>
      {targetCamp && (
       <ImageBox ref={imgRef}>
          {targetCamp.images.map((img, index) => (
            <img key={index} src={img} art="상세페이지 이미지" />
          ))} 
       </ImageBox>
      )}
    </Container>
  );

코드 설명

ContentsSection 컴포넌트 안에서, ContentsSection의 clientHeight값과, ImageBox안의 이미지들을 구하고 싶습니다. ContainerImageBox 컴포넌트를 useRef로 받습니다. containerRefimgRef 모두 DOM을 참조하기 위함입니다.

containerRef.current
imgRef.current

이 부분에서, 개체가 null인 것 같다는 오류가 뜹니다.

문제 해결 후 코드

interface IProps {
  targetCamp: ICampDetail;
}

const ContentsSection = ({ targetCamp }: IProps) => {
  const containerRef = useRef<HTMLElement>(null);
  const imgRef = useRef<HTMLElement>(null);
  const [height, setHeight] = useState<number>(0);

  const onloadImages = useCallback(() => {
    if (imgRef.current) { // if문 추가
      const imgs = imgRef.current.querySelectorAll('img');
      for (let i = 0; i < imgs.length; i++) {
        imgs[i].onload = () => {
          if (i === imgs.length - 1) getHeight();
        };
      }
    }
  }, []);

  const getHeight = () => {
    containerRef.current && // AND연산자 추가
      setHeight(containerRef.current.clientHeight);
  };

  useEffect(() => {
    targetCamp && onloadImages();
  }, [onloadImages, targetCamp]);

  return (
    <Container ref={containerRef}>
      {targetCamp && (
       <ImageBox ref={imgRef}>
          {targetCamp.images.map((img, index) => (
            <img key={index} src={img} art="상세페이지 이미지" />
          ))} 
       </ImageBox>
      )}
    </Container>
  );

if문과 AND연산자를 이용했습니다.
초기값이 null이어서, null이 반환되면 안되니 조건을 추가해서 에러를 해결했습니다.

useRef 정의

useRef를 사용하다보면, 심심찮게 에러를 뿜어내고는 하는데
이 참에, useRef 대해 이 문서를 보고 자세히 공부를 해봐야 겠다고 생각하여,
https://driip.me/7126d5d5-1937-44a8-98ed-f9065a7c35b5
이 문서를 보고 공부를 해보았습니다.

useRef의 반환타입은 2가지

interface MutableRefObject<T> {
    current: T;
}

interface RefObject<T> {
    readonly current: T | null;
}

useRef의 반환타입에는 2가지가 있고, 그저 함수 초깃값을 .current에 저장할 뿐입니다.

useRef의 정의는 3가지

useRef<T>(initialValue: T): MutableRefObject<T>; // 1
useRef<T>(initialValue: T|null): RefObject<T>; // 2
useRef<T = undefined>(): MutableRefObject<T | undefined>; // 3
  1. 인자의 타입과, 제네릭 타입이 T로 일치하는 경우, MutableRefObject<T>를 반환
  2. 인자의 타입이 null을 허용하는 경우, RefObject<T>를 반환
  3. 제네릭의 타입이 undefined인 경우(타입을 제공하지 않은 경우), MutableRefObject<T | undefined>를 반환

상황별 useRef의 사용

  1. useRef로 로컬변수 사용하기
  • 타입 과 인자가 "서로 같은 타입"인 경우, MutableRefObject<T> 반환
  • current 프로퍼티 그 자체를 직접 변경할 수 있습니다.
const localValRef = useRef<number>(숫자)

[예시코드]
: 버튼을 클릭할 경우 localVarRef.current의 값이 1씩 증가하는 코드 입니다.

import React, { useRef } from "react";

const App = () => {
  const localVarRef = useRef<number>(0);

  const handleButtonClick = () => {
		if (localVarRef.current) {
	    localVarRef.current += 1;
	    console.log(localVarRef.current);
		}
  };

  return (
    <div className="App">
      <button onClick={handleButtonClick}>+1</button>
    </div>
  );
};

export default App;
  1. useRef로 HTML엘리먼트 객체를 참조하기
  • 인자에 null을 허용하는 타입(T|null)을 할당할 경우, RefObject<T> 반환
  • current 프로퍼티를 직접 수정할 수 없습니다.
const containerRef = useRef<HTMLElement>(null)

나의 코드에서는 어떤 타입을 반환하는가?

내 코드에서는 DOM을 참조하고 싶은거니까, RefObject<T>를 반환합니다.

RefObject<T>는 값을 수정할 수 없다고 하는데, 왜 정상적으로 작동할까?

정의 상 current 프로퍼티만 읽기 전용으로, current 프로퍼티의 하위 프로퍼티는 여전히 수정 가능하다. 이는 readonly가 shallow(얕은객체복사)이기 때문입니다.

Ref 타입설정?

const containerRef = useRef() as React.MutableRefObject<HTMLElement>;
const imgRef = useRef() as React.MutableRefObject<HTMLElement>;

useRef의 타입을 이렇게 설정하면, MutableRefObject<T>를 반환하는 것을
확인하였습니다. 이 경우, onloadImagesgetHeight에 각각 if문이나,
AND연산자를 걸지 않아도 동작합니다.

  const onloadImages = useCallback(() => {
    const imgs = imgRef.current.querySelectorAll('img');
    for (let i = 0; i < imgs.length; i++) {
      imgs[i].onload = () => {
        if (i === imgs.length - 1) getHeight();
      };
    }
  }, []);

  const getHeight = () => {
   setHeight(containerRef.current.clientHeight);
  };

정리

정리하면 다음과 같습니다.

const localVarRef = useRef<여기>(저기);

로컬 변수 용도로 useRef를 사용하는 경우, MutableRefObject<T>를 사용해야 하므로
제네릭 타입과 같은 타입의 초깃값을 넣어주자. (여기와 저기를 같게)

const inputRef = useRef<HTMLInputElement>(null);

DOM을 직접 조작하기 위해 프로퍼티로 useRef 객체를 사용할 경우,
RefObject<T>를 사용해야 하므로 초깃값으로 null을 넣어주자.

useRef() as React.MutableRefObject<T>

MutableRefObject<T>를 반환한다!

profile
코드치는 개발자가 아닌 생각하는 개발자!
post-custom-banner

0개의 댓글