공부/ReactNative

[ReactNative] useMemo, 커스텀 Hooks | 처음 배우는 리액트네이티브 6-4장, 6-5장

hyunh404 2025. 1. 22. 21:35
728x90

 

 

useMemo

 

 

  • 동일한 연산 반복 수행 제거 => 성능 최적화
  • 첫번째 파라미터 = 함수
  • 두번째 파라미터 = 함수 실행 조건 => 배열로 전달

 

JavaScript, Expo, React Native로 배열을 설정하고 버튼을 클릭할 때마다 배열을 순환하며 문자열 길이를 구해보겠다.

 

그 결과,

버튼 클릭 시 문자열 길이를 구했으며

마지막 문자열 이후에는 더이상 변화가 없기에 길이를 반복해서 구하는 것을 확인했다.

 

import React, { useState } from 'react';
import styled from 'styled-components/native';
import Button from './Button';

const StyledText = styled.Text`
  font-size: 24px;  
`;

const getLength = text => {
    console.log(`Target Text: ${text}`);
    return text.length;
};

const list = ['JavaScript', 'Expo', 'Expo', 'React Native'];

let idx = 0;
const Length = () => {
    const [text, setText] = useState(list[0]);
    const [length, setLength] = useState('');

    const _onPress = () => {
        setLength(getLength(text));
        ++idx;
        if (idx < list.length) setText(list[idx]);
    };

    return (
        <>
            <StyledText>Text: {text}</StyledText>
            <StyledText>Length: {length}</StyledText>
            <Button title="Get Length" onPress={_onPress} />
        </>
    )
}

export default Length

중복 연산 O

 

 

이 코드에서는 동일한 문자열 길이를 계산하고 변화가 없는데도 함수가 호출된다는 아쉬운 점이 존재한다.

이를 해결하기 위해 useMemo를 사용하면 된다.

 

 

useMemo를 사용하면 값에 변화가 있는 경우에만 함수가 호출되어

불필요한 중복 연산을 제거할 수 있다.

 

 

text 값에 변화가 있을 경우에만 길이를 구하도록 수정해주었다.

따라서 동일한 문자열과 마지막 값 이후 함수가 호출되지 않는 것을 확인할 수 있었다.

const _onPress = () => {
    ++idx;
    if (idx < list.length) setText(list[idx]);
};

const length = useMemo(() => getLength(text), [text]);

useMemo => 중복 연산 X

 

 

728x90

 

 

커스텀 Hooks

 

 

  • 사용자 자유 Hook
  • 네이티브에서는 네트워크 통신을 위해 Fetch, XMLHttpRequest, WebSocket 지원
  • 자주 사용하는 코드를 분리해 깔끔한 코드 정리, 재사용 가능

 

커스텀 Hook으로 특정 API에 GET 요청을 보내고 응답을 받는 함수를 만들어보려고 한다.

 

네이티브에서 제공하는 Fetch를 이용해 useFetch Hook을 만들겠다.

 

import { useEffect, useState } from "react";

export const useFetch = url => {
    const [data, setData] = useState(null);
    const [error, setError] = useState(null);

    useEffect(async () => {
        try {
            const res = await fetch(url);
            const result = await res.json();
            if (res.ok) {
                setData(result);
                setError(null);
            } else {
                throw result;
            }
        } catch (error) {
            setError(error);
        }
    }, []);

    return (
        { data, error }
    )
};

 

 

위 코드 처럼

API 주소를 전달받아 요청을 보내고 성공 여부에 따라 데이터를 반환하는 useFetch.jsx를 만들어 주었다.

 

 

이제 커스텀 Hook을 이용해서 API를 요청해보겠다.

https://dog.ceo/api/breeds/image/random

 

 

위 url을 이용해 강아지 이미지를 불러오는 API를 작성하고 useFetch Hook을 사용해 컴포넌트를 만들었다.

 

import React from 'react';
import styled from 'styled-components/native';
import { useFetch } from '../hooks/useFetch';

const StyledImage = styled.Image`
    background-color: #7f8c8d;
    width: 300px;
    height: 300px;
`;

const ErrorMessage = styled.Text`
    font-size: 18px; 
    color: #e74c3c;
`;

const URL = 'https://dog.ceo/api/breeds/image/random';
const Dog = () => {
    const { data, error } = useFetch(URL);

    return (
        <>
            <StyledImage source={data?.message ? { uri: data.message } : null} />
            <ErrorMessage>{error?.message}</ErrorMessage>
        </>
    )
}

export default Dog

 

 

 

그 결과, 다음과 같은 에러가 발생했다.

Warning: useEffect must not return anything besides a function, which is used for clean-up.

 

 

이는 useEffect의 첫번째 파라미터로 비동기 함수를 전달했기 때문에 나타난다.

비동기 함수를 이용할 때는 useEffect에 전달되는

함수 내부에 비동기 함수를 정의하고 사용함으로써 문제를 해결할 수 있다.

 

 

따라서 커스텀 Hook인 useFetch에서 useEffect 내부에 fetchData 함수를 정의해 비동기 함수를 정의하고,

정의한 비동기 함수를 호출해줌으로써 문제를 해결했다.

 

useEffect(() => {
    const fetchData = async () => {
        try {
            const res = await fetch(url);
            const result = await res.json();
            if (res.ok) {
                setData(result);
                setError(null);
            } else {
            	throw result;
            }
        } catch (error) {
        	setError(error);
        }
    };

    fetchData();
}, []);

useFetch 활용

 

 

💡 "비동기" 란?

작업을 실행할 때 작업이 완료되지 않더라도 다음 코드 실행하는 방식
즉, 작업이 완료되는 결과를 기다리지 않고 다음 코드를 실행해 시간을 절약하고 병렬적인 작업 처리 가능
사용자의 요청에 빠르게 반응할 수 있도록 하기 위한 설계

● 반응성, 성능 향상
● 작업 시간 단축
● 에러 캐치 및 처리 가능 → 안정성 향상

 

 

  • 대부분의 비동기 작업은 완료 전에 화면 전체, 특정 버튼들이 사용 불가 상태로 변경됨
  • 비동기 동작에서는 선행 작업 마무리 전 추가 요청 들어오지 않도록 하는 것이 좋음
  • API 진행상태 추적 => 작업 상태에 따라 화면 구성 변경

 

마지막으로 작업의 진행 상태를 추적해 화면 구성을 변경해보려고 한다.

inProgress를 useState로 정의해주고

API 요청 시작 전과 완료 후 상태를 변경해 진행 상태를 확인했다.

 

import { useEffect, useState } from "react";

export const useFetch = url => {
    const [data, setData] = useState(null);
    const [error, setError] = useState(null);
    const [inProgress, setInProgress] = useState(false);

    useEffect(() => {
        const fetchData = async () => {
            try {
                setInProgress(true);
                const res = await fetch(url);
                const result = await res.json();
                if (res.ok) {
                    setData(result);
                    setError(null);
                } else {
                    throw result;
                }
            } catch (error) {
                setError(error);
            } finally {{
                setInProgress(false);
            }}
        };

        fetchData();
    }, []);

    return (
        { data, error, inProgress }
    )
};
{inProgress && (
	<LoadingMessage>The API request is in progress</LoadingMessage>
)}

 

 

 

진행 상태를 받아서 상태에 따라 화면이 변경될 수 있도록

API 요청이 아직 로딩되지 않았을 때에는 로딩메세지가 뜨게 만들어 주었다.

 

 

요청 진행 상태를 활용해 로딩 메세지 설정

728x90