본문 바로가기
카테고리 없음

중복확인 버튼없이 패스워드 입력으로 넘어가면 중복여부를 확인하도록 수정해줘

by 두잇건강 2025. 8. 19.
반응형

# 입력 이동 시 자동 중복 검사로 수정하기 (버튼 없이)

아래 코드는 “중복 확인” 버튼 없이, 아이디 입력 후 포커스가 비밀번호 입력으로 이동하는 순간(blur 또는 Enter로 다음 필드 포커스) 자동으로 중복 여부를 검사하도록 변경한 예시입니다. 기존 JS 버전(Composition API)을 기준으로 수정했습니다.

핵심 변경 사항
- 디바운스 실시간 검사 대신, 아이디 인풋에서 포커스가 빠질 때(blur) 또는 Enter로 다음 필드로 이동할 때 중복 검사 실행.
- 별도의 “중복 확인” 버튼 제거.
- 회원가입 버튼 활성화 조건은 동일하게 유지: 형식 OK && 중복 아님 && 에러 없음.

## 1) 컴포저블 업데이트 (composables/useUsernameCheck.js)
- watch에 의한 자동 디바운스 호출 제거.
- 외부에서 이벤트로 호출할 수 있는 checkNow를 그대로 사용.
- 형식 검사는 입력 시에도 즉시 반영하고 싶다면 watch로 형식만 반영하도록 분리.

```js
// composables/useUsernameCheck.js
import { ref, computed, watch } from 'vue';
import { checkUsernameExists } from '@/api/auth';

const USERNAME_REGEX = /^[a-z0-9_]{4,16}$/;

export function useUsernameCheck() {
  const username = ref('');
  const isFormatValid = ref(null);   // true | false | null
  const isChecking = ref(false);
  const isDuplicate = ref(null);     // true | false | null
  const error = ref(null);

  const validateFormat = (v) => {
    if (!v) return null;
    return USERNAME_REGEX.test(v);
  };

  // 입력 중에는 형식만 즉시 업데이트 (서버 호출은 blur 시점에만)
  watch(username, (v) => {
    isFormatValid.value = validateFormat(v);
    // 사용자가 수정 중이면 이전 중복 결과는 무의미하므로 리셋
    isDuplicate.value = null;
    error.value = null;
  });

  const doCheck = async (v) => {
    const valid = validateFormat(v);
    isFormatValid.value = valid;

    // 유효하지 않거나 비어 있으면 서버 호출하지 않음
    if (!v || !valid) {
      isDuplicate.value = null;
      return;
    }

    try {
      isChecking.value = true;
      const exists = await checkUsernameExists(v);
      isDuplicate.value = exists;
      error.value = null;
    } catch (e) {
      error.value = '아이디 중복 확인 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.';
      isDuplicate.value = null;
    } finally {
      isChecking.value = false;
    }
  };

  // 외부에서 호출할 메서드: blur나 Enter 시점에 호출
  const checkNow = () => doCheck(username.value);

  const canSubmit = computed(() => {
    return (
      isFormatValid.value === true &&
      isDuplicate.value === false &&
      !isChecking.value &&
      !error.value
    );
  });

  return {
    username,
    isFormatValid,
    isChecking,
    isDuplicate,
    error,
    checkNow,
    canSubmit,
  };
}
```

## 2) 컴포넌트 수정 (components/SignUpUsernameField.vue)
- 버튼 제거.
- 아이디 인풋에서 blur 시점에 checkNow 호출.
- Enter로 비밀번호 필드로 이동하는 경우에도 검사되도록 keydown.enter에서 먼저 checkNow를 await한 후 다음 필드에 포커스 이동.
- 비밀번호 인풋에 ref를 주어 포커스 제어.

```vue
<!-- components/SignUpUsernameField.vue -->
<script setup>
import { computed, ref, nextTick } from 'vue';
import { useUsernameCheck } from '@/composables/useUsernameCheck';

const {
  username,
  isFormatValid,
  isChecking,
  isDuplicate,
  error,
  checkNow,
  canSubmit,
} = useUsernameCheck();

const password = ref('');
const passwordInputRef = ref(null);

const helperText = computed(() => {
  if (!username.value) return '소문자/숫자/언더스코어 4~16자';
  if (isChecking.value) return '아이디 확인 중...';
  if (error.value) return error.value;
  if (isFormatValid.value === false) return '형식이 올바르지 않습니다.';
  if (isDuplicate.value === true) return '이미 사용 중인 아이디입니다.';
  if (isDuplicate.value === false) return '사용 가능한 아이디입니다.';
  return '';
});

// Enter로 비밀번호로 이동하려 할 때, 먼저 중복검사 실행 후 포커스 이동
const onUsernameEnter = async (e) => {
  e.preventDefault();
  await checkNow();
  await nextTick();
  if (passwordInputRef.value) {
    passwordInputRef.value.focus();
  }
};

// blur로 비밀번호로 넘어갈 때도 검사 실행
const onUsernameBlur = async () => {
  await checkNow();
};
</script>

<template>
  <div class="field">
    <label for="username">아이디</label>
    <input
      id="username"
      v-model.trim="username"
      type="text"
      placeholder="아이디를 입력하세요"
      autocomplete="username"
      inputmode="latin"
      @blur="onUsernameBlur"
      @keydown.enter="onUsernameEnter"
    />

    <p
      class="helper"
      :class="{
        error: isFormatValid === false || isDuplicate === true || !!error,
        success: isDuplicate === false
      }"
      aria-live="polite"
    >
      {{ helperText }}
    </p>

    <label for="password">비밀번호</label>
    <input
      id="password"
      ref="passwordInputRef"
      v-model="password"
      type="password"
      placeholder="비밀번호를 입력하세요"
      autocomplete="new-password"
    />

    <button type="submit" :disabled="!canSubmit">회원가입</button>
  </div>
</template>

<style scoped>
.field { display: grid; gap: 8px; }
.helper { font-size: 12px; margin: 0; }
.helper.error { color: #d32f2f; }
.helper.success { color: #2e7d32; }
</style>
```

## 동작 요약
- 사용자가 아이디를 입력하는 동안에는 형식 유효성만 실시간 표시.
- 아이디 인풋에서 포커스를 잃거나 Enter로 다음 필드로 이동하면 서버에 중복 확인 요청.
- 결과에 따라 “사용 가능/이미 사용 중/오류” 상태 메시지 업데이트.
- 회원가입 버튼은 형식 OK, 중복 아님일 때만 활성화.

## 추가 팁
- 같은 아이디에 대한 반복 호출을 줄이려면 마지막 검사 값 캐싱(Map) 추가를 고려.
- blur/enter 이외에도 “폼 제출 시” 서버에서 최종 재검증은 필수. 경쟁 상태를 방지하기 위함.
- 레이턴시가 길다면 비밀번호 필드로 포커스는 즉시 이동하되, 아이디 영역에 로딩 상태를 보여주는 현재 UX가 일반적입니다.

출처

반응형