고윤태의 개발 블로그

누구나 접근하기 쉽지만 얻어 가는 것이 있는 글을 쓰기 위해 노력 중입니다.

개발

React Native useImageAspectRatio 이미지 비율을 계산하는 hook

고윤태 2024. 1. 30. 10:03
안녕하세요 이번에 작성하게 될 내용은 제가 react-native에서 useImageAspectRatio custom hook을 구현한 이유와 방식에 대해 공유하고자 글을 작성하게 되었습니다.

💻 개발 환경

React-native(CLI) + TypeScript로 진행되었습니다.

🧐 구현하게 된 계기

이번에 회사에서 UI 리뉴얼 작업을 진행했습니다. 진행 도중 최대한 활용할 수 있는 것은 하되 프로젝트를 상하게 만드는 코드는 거침없이 리팩토링을 진행하자가 제 지조였습니다.

(상하게 만드는 코드 : 개인적으로 사용하는 말이지만 설명을 드리자면 스파게티 코드 또는 레거시 등 프로젝트의 곰팡이를 생기게 할 수 있는 코드를 뜻 합니다)

 

 

위의 사진과 같은 UI로 Home 화면이 변경이 되었고 최상단에서 Banner에 이미지를 제공했어야 했습니다.

유명한 애플리케이션인 배민, 컬리, 무신사 등에만 접속해도 흔하게 볼 수 있는 UI입니다.

이제 디자이너 분이 원하시는 이미지는 이미지의 width는 양 옆 padding 16을 제외한 채 꽉 찬 상태에서 width에 맞게 height가 비율에 맞게 늘어나는 것을 원하셨다.

 

            <Image
              source={{ uri: item.image }}
              style={{
                width: '100%',
                borderRadius: 12,
                height: 'auto',
              }}
              resizeMode="contain"
            />

 

당연히 저는 Web에서 css의 기억을 이용하여 이런 식으로 스타일링을 했지만 이미지는 나오지 않았습니다.

 

 

GPT에 질문을 통해 React Native에서 auto 속성은 지원하지 않는다는 것을 확인했습니다.

 

세상이 무너졌다.

그렇다면 저는 이번을 통해서 새로운 지식을 습득했고 이 상황을 어떻게 해결할지 고민을 해봤습니다.

 

 

GPT를 심문한 결과 아주 좋은 힌트들을 얻었습니다.

  • 이미지 비율을 계산하는 방법
  • 이미지의 사이즈를 가져오는 방법

2개면 GPT가 충분한 정보를 제공해 줬다고 생각합니다.

이것을 토대로 코드로 옮겨보겠습니다.

(너무나도 명확하게 정보들을 수집했기에 "기능 산출 및 개발 계획"은 스킵하도록 하겠습니다.)

👀 custom hook (CODE)

import { useState, useEffect } from 'react';
import { Image } from 'react-native';

const useImageAspectRatio = (imageUrl?: string) => {
  const [aspectRatio, setAspectRatio] = useState<null | number>(null);

  useEffect(() => {
    let isValid = true;

    if (imageUrl) {
      Image.getSize(
        imageUrl,
        (width, height) => {
          if (isValid) {
            setAspectRatio(width / height);
          }
        },
        () => {
          console.log('useImageAspectRatio Error');
        }
      );
    }

    return () => {
      isValid = false;
    };
  }, [imageUrl]);

  return aspectRatio;
};

export default useImageAspectRatio;

코드 설명

aspectRatio라는 상태 변수를 선언하고, 초기 값으로 null을 설정합니다. 이 변수는 계산된 이미지의 가로세로 비율을 저장하기 위해 사용됩니다.

 

useEffect 안에서는 다음과 같은 로직이 수행됩니다:

  • isValid 변수: 컴포넌트가 언마운트될 때 비동기 작업을 취소하기 위해 isValid라는 지역 변수를 선언합니다. 이 변수는 useEffect의 클린업 함수에서 false로 설정되어, 컴포넌트가 언마운트된 후에는 Image.getSize의 콜백이 상태를 변경하지 않도록 합니다.
  • Image.getSize: Image 모듈의 getSize 함수를 사용하여 이미지의 너비와 높이를 가져옵니다. 이 함수는 세 개의 매개변수를 받습니다:
    • imageUrl: 이미지의 URL
    • 성공 콜백: 이미지의 너비와 높이를 받아 aspectRatio 상태를 업데이트합니다.
    • 실패 콜백: 에러가 발생했을 때 호출되는 함수입니다.

컴포넌트 언마운트 시 클린업: useEffect의 반환 함수에서 isValid 변수를 false로 설정하여, 컴포넌트가 언마운트되었을 때 이미지 크기 정보의 비동기 요청이 완료되더라도 상태 업데이트가 수행되지 않도록 합니다.

 

반환 값: aspectRatio 상태 변수를 반환합니다. 이 값은 이미지의 가로세로 비율을 나타냅니다.

 완성된 custom hook

테스트 이미지가 필요하신 분은 아래 이미지를 다운로드 후 사용하셔도 좋을 것 같습니다. 제가 작업했던 배너와 동일한 사이즈의 이미지를 준비했습니다.

 

예제 코드

import React, { useState, useEffect } from 'react';
import { View } from 'react-native';
import FastImage from 'react-native-fast-image';
import useImageAspectRatio from '../../../../hooks/useImageAspectRatio';
import axios from 'axios';

const NoticeBanner = () => {
  const [notices, setNotices] = useState<string>('');
  const aspectRatio = useImageAspectRatio(notices);

  const fetchNotice = async () => {
    try {
      const banner = await axios.get('rest-api');
      setNotices(banner.data);
    } catch (err) {
      console.log(err);
    }
  };

  useEffect(() => {
    fetchNotice();
  }, []);

  return (
    <View
      style={{
        position: 'relative',
        paddingHorizontal: 16,
      }}>
      <FastImage
        source={{ uri: notices }}
        style={{
          width: '100%',
          ...(aspectRatio !== null && {
            aspectRatio,
          }),
        }}
        resizeMode="contain"
      />
    </View>
  );
};

export default NoticeBanner;

 

결과 화면

😁 글 작성 후기

이 경험을 통해 React Native에 대한 CSS(Style) 지식이 하나 더 생긴 순간이었습니다.

기존에 될 것이라 생각하던 방식이 작동하지 않아 당황을 하긴 했지만 Chat GPT를 잘 심문한 덕분에 비교적으로 잘 헤쳐나갔습니다(?)

 

평소에 개발할 때도 틈틈이 Chat GPT를 유용하게 사용하고 있는 편이었지만 최근에 재밌게 읽었던 글들 중에 Chat GPT를 활용하여 서비스를 만들기 또는 Chat GPT가 프론트엔드 개발자를 대체할 수 있을까? 와 같은 글을 읽고서 궁금증이 생겨서 GPT를 이용하여 문제를 해결해 보자 라는 마음이 생긴 시점이었는데 활용하기 딱 좋은 상황이 나와서 시도해 볼 수 있는 너무나 좋은 경험이었습니다.

 

오늘 만든 hook은 저와 같은 상황에 놓여있다면 프로젝트의 이점을 만들어주고 다양한 곳에서 쓰일 가능성은 높진 않지만 현재 상황의 극복을 도와주는 매우 훌륭한 hook이라고 생각합니다.

다들 활용해야 하는 기회가 온다면 활용해 보시면 좋을 것 같습니다!

 

읽어주셔서 감사합니다!