React & React Native/라이브러리 활용

[RN] react-native 버튼 중복 호출 막는 방법 : lodash Debounce + useCallback

adjh54 2024. 4. 9. 19:00
반응형
해당 글에서는 버튼의 중복 호출을 막기 위해 lodash 라이브러리의 Debounce 모듈을 활용하는 방법에 대해 알아봅니다.


1) 문제점 파악


💡 문제점 파악

- SetState()로 버튼을 누르면 즉각적으로 버튼에 대해 disabled = true 처리를 하면 해결이 되지만, 부모와 자식 구조간에 관계에서 빠르게 처리가 되지 않아서 중복으로 함수 처리가 되는 문제가 발생하였습니다.

- 이에 따라서 버튼을 여러 번 빠르게 눌러도 한 번의 처리인 것과 같은 기능을 구현하고 싶어서 찾아보는 중 lodash 라이브러리의 Debounce 모듈을 알게 되어 이를 적용하였습니다.

 
 

2) lodash


💡 lodash

- JavaScript에서 사용할 수 있는 유틸리티 라이브러리입니다.
- 배열, 객체, 함수, 언어 유틸, 숫자, 문자열, 날짜, 논리 등과 같은 다양한 데이터 타입을 처리하는 데 도움이 되는 수많은 메서드를 제공합니다.
- 이 라이브러리를 사용하면 코드를 보다 명확하고 간결하게 작성할 수 있습니다. 또한 Lodash는 모듈화가 되어 있어 필요한 기능만 선택적으로 사용할 수 있으므로, 애플리케이션의 성능 향상에도 기여할 수 있습니다.

 
 

1. lodash 주요 모듈


모듈명 설명
_.chunk 배열을 지정된 크기의 여러 부분으로 나눕니다.
_.compact 배열에서 거짓 값(false, null, 0, "", undefined, NaN)을 제거합니다.
_.concat 배열 또는 값들을 연결하여 새 배열을 만듭니다.
_.difference 첫 번째 배열에는 있지만 두 번째 배열에는 없는 값을 반환합니다.
_.drop 배열의 첫 번째 요소를 제거하고 나머지 요소를 반환합니다.
_.debounce 연속적으로 호출되는 함수의 실행을 일정 시간 동안 ‘지연’시킵니다.

 
 
 
 

2. lodash Debounce


💡 lodash Debounce

- 연속적으로 호출되는 함수의 실행을 일정 시간 동안 ‘지연’시키는 역할을 합니다. 이는 사용자에게서 이벤트가 여러 번 발생할 경우, 이벤트 핸들러가 과도하게 호출되는 것을 방지하고, 성능을 향상하는 데 도움이 됩니다.

- 예를 들어, 버튼을 누르면 DB INSERT를 수행하는 API를 호출하는 함수가 있다고 가정합니다.
- 그리고 해당 함수는 lodash Debounce로 감싸져 있고, 시간을 5000으로 지정했습니다.
- 그래서 사용자는 연속으로 해당 버튼을 연타를 누릅니다. 누르게 되면 5초(5000) 동안에 발생하는 이벤트 중 제일 마지막에 누른 1번만 수행을 합니다. 단 5초가 지날 때까지 감싸진 함수는 수행되지 않습니다.

 
 

3) lodash Debounce를 이용한 버튼 제어


 

1. 라이브러리 설치


$ npm install lodash
# or
$ yarn add lodash

# typescript

$ npm install --save @types/lodash
# or
$ yarn add --dev @types/lodash

 

 

lodash

Lodash modular utilities.. Latest version: 4.17.21, last published: 3 years ago. Start using lodash in your project by running `npm i lodash`. There are no other projects in the npm registry using lodash.

www.npmjs.com

 
 

 

2. lodash Debounce를 이용한 버튼 제어 예시


💡 lodash Debounce를 이용한 버튼 제어 예시

- TestScreen로 컴포넌트 화면을 구성하였습니다. ‘버튼 테스트를 수행합니다.’라는 버튼을 누르면 onPressTest() 함수가 수행되는 구조입니다.
- onPressTest() 함수는 lodash의 debounce 함수로 감싸져 있어서 버튼을 누르면 5초 뒤에 수행이 됩니다.
- 이 5초가 수행되는 과정에 여러번 버튼을 터치한 경우에 5초가 지나기 전 제일 마지막에 누른 이벤트만 수행을 합니다.
import React from "react";
import { Text, TouchableOpacity } from "react-native";
import { View } from "react-native";
import _ from "lodash";

const TestScreen = () => {

    const onPressTest = _.debounce(() => {
        console.log("[+] 여러번 반복 터치를 수행합니다.");
    }, 5000)

    // or
    
    const pressHandler = (() => {
        return {
            onPressTest: _.debounce(() => {
                console.log("[+] 여러번 반복 터치를 수행합니다.");
            }, 5000)
        }
    })();

    return (
        <View>
            <TouchableOpacity onPress={onPressTest} style={{ backgroundColor: "red", height: 200 }}>
                <Text>버튼 테스트를 수행합니다.</Text>
            </TouchableOpacity>
        </View>
    )

}
export default TestScreen

 
 
 

3. lodash Debounce 문제점


💡 문제점

- 컴포넌트가 리 렌더링 될때마다 debounce 함수는 재생성되므로 debounce가 초기화됩니다. 따라서 연속적으로 이벤트가 발생하더라도 debounce 함수는 새롭게 시작되므로 원하는 디바운스 효과를 얻지 못할 수 있습니다.

- 그렇기에 이 문제를 해결하기 위해서는 debounce 함수를 컴포넌트 외부에 정의하거나, React의 useCallback Hook을 이용하여 함수를 메모이제이션 하는 방법이 일반적으로 사용됩니다.

 
 

 💡 아래의 코드에서는 TestScreen 컴포넌트가 리렌더링 될 때마다 onPressTest 함수를 재생성합니다. 이 때문에 해당 함수는 lodash의 debounce 함수의 효과를 제대로 발휘하지 못하게 됩니다.
import React from "react";
import { Text, TouchableOpacity } from "react-native";
import { View } from "react-native";
import _ from "lodash";

const TestScreen = () => {

    const onPressTest = _.debounce(() => {
        console.log("[+] 여러번 반복 터치를 수행합니다.");
    }, 5000)

    // or
    
    const pressHandler = (() => {
        return {
            onPressTest: _.debounce(() => {
                console.log("[+] 여러번 반복 터치를 수행합니다.");
            }, 5000)
        }
    })();

    return (
        <View>
            <TouchableOpacity onPress={onPressTest} style={{ backgroundColor: "red", height: 200 }}>
                <Text>버튼 테스트를 수행합니다.</Text>
            </TouchableOpacity>
        </View>
    )

}
export default TestScreen

 
 
 

4. useCallback을 이용한 해결방법


💡 useCallback을 이용한 해결방법

- useCallback을 사용하여 onPressTest 함수를 메모이제이션하였습니다. 이로써, 컴포넌트가 리렌더링 되더라도 onPressTest 함수는 새롭게 생성되지 않고 메모리에 저장된 이전 함수가 반환됩니다.
- 이를 통해서 디바운스 효과를 유지하는 데 필요한 조건을 충족시킵니다.
import React, { useCallback } from "react";
import { Text, TouchableOpacity } from "react-native";
import { View } from "react-native";
import _ from "lodash";

const TestScreen = () => {

    const onPressTest = useCallback(_.debounce(() => {
        console.log("[+] 여러번 반복 터치를 수행합니다.");
    }, 5000), [])

    // or

    const pressHandler = (() => {
        return {
            onPressTest: useCallback(_.debounce(() => {
                console.log("[+] 여러번 반복 터치를 수행합니다.");
            }, 5000), [])
        }
    })();

    return (
        <View>
            <TouchableOpacity onPress={onPressTest} style={{ backgroundColor: "red", height: 200 }}>
                <Text>버튼 테스트를 수행합니다.</Text>
            </TouchableOpacity>
        </View>
    )

}
export default TestScreen

 
 

💡[참고] useCallback 이란?

- 메모이제이션이 된 콜백을 반환하는 Hook을 의미합니다. 주로 렌더링 사이에 이 함수를 기억할 때 유용합니다.

- 첫 번째 매개변수로 콜백, 두 번째 매개변수로 의존성 배열을 받습니다.
- useCallback을 사용하면, 의존성 배열에 있는 값들이 변경될 때만 콜백이 다시 생성됩니다. 이는 비용이 많이 드는 연산을 피하고, 함수의 참조 일관성을 유지하는데 도움이 됩니다.

 

💡 사용예시

- memoizedCallback() 함수의 경우 a 또는 b의 값이 변경되는 경우에 해당 함수가 다시 생성이 됩니다.
const memoizedCallback = useCallback(() => {
    doSomething(a, b);
  },[a, b]
);

 
 
 

4) 테스트 결과


💡 테스트 결과

- 아래와 같은 코드 내에서 버튼을 터치하면 “[+] 버튼을 반복 터치합니다.”이라는 콘솔을 출력하는 한편 onPressTest() 함수를 호출합니다.
- 실제 터치를 했을 때는 콘솔이 나오지만, onPressTest() 함수는 1초간 한 번만 호출되는 처리를 확인할 수 있었습니다.
import React, { useCallback } from "react";
import { Text, TouchableOpacity } from "react-native";
import { View } from "react-native";
import _ from "lodash";

const TestScreen = () => {

    const onPressTest = useCallback(_.debounce(() => {
        console.log("[+] 실제 함수의 호출")
    }, 1000), [])

    return (
        <>
            <View>
                <TouchableOpacity onPress={() => {
                    console.log("[+] 버튼을 반복 터치합니다.")
                    onPressTest()
                }}
                    style={{ backgroundColor: "red", height: 200 }}>
                    <Text>버튼 테스트를 수행합니다.</Text>
                </TouchableOpacity>
            </View>
        </>
    )

}
export default TestScreen

 
 
 
 
 
오늘도 감사합니다. 😀
 
 
 
 

반응형