본문 바로가기
Dev/Vue.js

[Vue] 중복 클릭 방지 방법

by YummYum 2024. 12. 7.

 

버튼을 클릭하면 서버에 요청이 한 번만 들어가야 합니다. 물론 서버에서도 여러 번의 요청이 오더라도 1번 처리하도록 해야 하지만 프론트에서 처리하는 방법도 있습니다.

 

1. 클릭 시 disabled 처리

중복 클릭을 막는 방법으로 제일 처음 생각하게 되는 것은 disabled 처리일 것입니다. 클릭을 하면 버튼에 disabled 처리를 해 비활성화를 시키고 서버에서의 응답을 받은 후에 버튼을 다시 활성화시키는 것이다.

 

처음에는 저 또한 이러한 방법으로 처리했습니다. 하지만 버튼이 비활성화되는 것보다 더 빠르게 중복 클릭이 되어 2번 요청이 들어와 시스템 상에 오류가 발생하는 일이 있었습니다.

 


 

2. 디바운싱 처리

이러한 오류를 해결하기 위해 디바운싱을 이용하기로 했습니다. 디바운싱이랑 연이어 호출되는 함수들 중 마지막(또는 제일 처음)만 호출하도록 하는 것입니다. 가장 처음 클릭만 허용하고 1초 동안은 그 어떠한 클릭이 일어나더라도 무시하도록 합니다.

 

** 디바운싱과 함께 언급되는 쓰로트링도 있는데 일정한 주기마다 실행하는 것입니다. 예를 들어 scroll 이벤트가 발생할 때 복잡한 작업이 있다면 성능 상 문제가 발생해 scroll 이벤트에서 몇 초에 한 번씩만 실행되게 제한을 둡니다.

 

 

물론 클릭 이벤트가 실행되어 서버로 요청을 보내면 disabled 처리를 하고, 서버에서 응답이 올 경우 버튼이 활성화되도록 할 것 입니다. 그러기 위해 직접적으로 props를 명시적으로 선언하지 않고 폴스루 속성을 이용하였습니다. 제가 만들 컴포넌트는 싱글 루트 엘리먼트를 렌더링 하기 때문에 자동으로 속성이 추가됩니다. (class, style, id, v-on 이벤트 등)

 

<SubmitButton :disabled="isSumitted" :onClick="handleOnClick" />

 

<template>
  <button type="submit" @click.prevent="handleOnClick">
    <slot>버튼</slot>
  </button>
</template>
<script>
export default {
  name: 'SubmitButton',
  props: {
    onClick: { type: Function, required: true },
  },
  data() {
    return {
      timer: undefined,
    }
  },
  methods: {
    handleOnClick() {
      if (!this.timer) this.onClick()

      this.timer = setTimeout(() => {
        this.timer = undefined
      }, 1000)
    },
  },
}
</script>

 

 

제대로 중복 클릭이 방지가 되는지 스토리북에 테스트 코드를 작성합니다. 클릭 이벤트를 여러 발생시키고 실제로 버튼 이벤트가 1번말 발생했는지를 테스트 코드로 작성합니다.

 

import { expect, fn, userEvent, within } from '@storybook/test'
import SubmitButton from '@/components/atoms/SubmitButton.vue'

export default {
  title: 'components/atoms/SubmitButton',
  component: SubmitButton,
  parameters: {
    componentSubtitle:
      '처음 클릭 이벤트 후에 1초 동안 다른 클릭 이벤트가 일어나도 무시하도록 동작하는 버튼 컴포넌트입니다.',
  },
}

const handleSubmitButtonOnclick = fn(() => {})

export const Default = {
  args: {
    disabled: false,
    onClick: handleSubmitButtonOnclick,
  },
  play: async ({ canvasElement, step }) => {
    const canvas = within(canvasElement)
    const button = canvas.getByRole('button')

    await step('더블 클릭 방지 테스트', async () => {
      await userEvent.click(button)
      await userEvent.click(button)
      await userEvent.click(button)

      await expect(handleSubmitButtonOnclick).toHaveBeenCalledTimes(1)
    })
  },
}

 

 

스토리북에서 테스트 코드에 대한 결과 화면입니다.

 

댓글