아래는 앞서 제공한 TypeScript 예제를 모두 순수 JavaScript로 변환한 코드입니다. 타입 주석, 제네릭, zod 스키마 등 TS 전용 요소를 제거하고, JS 환경에서 그대로 동작하도록 다듬었습니다.
# 1) 디바운스 유틸 (utils/debounce.js)
```js
// utils/debounce.js
export function debounce(fn, delay = 300) {
let timer;
return (...args) => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
```
# 2) 중복 검사 API 호출 (api/auth.js)
```js
// api/auth.js
export async function checkUsernameExists(username) {
const res = await fetch('/api/auth/check-username', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username }),
});
if (!res.ok) {
throw new Error(`checkUsernameExists failed: ${res.status}`);
}
const data = await res.json();
return Boolean(data.exists);
}
```
# 3) 컴포저블: 아이디 중복 검사 로직 (composables/useUsernameCheck.js)
```js
// composables/useUsernameCheck.js
import { ref, watch, computed } from 'vue';
import { debounce } from '@/utils/debounce';
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);
};
const doCheck = async (v) => {
const valid = validateFormat(v);
isFormatValid.value = valid;
isDuplicate.value = null;
error.value = null;
if (!v || !valid) return;
try {
isChecking.value = true;
const exists = await checkUsernameExists(v);
isDuplicate.value = exists;
} catch (e) {
error.value = '아이디 중복 확인 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.';
isDuplicate.value = null;
} finally {
isChecking.value = false;
}
};
const debouncedCheck = debounce(doCheck, 400);
watch(username, (v) => {
if (!v) {
isFormatValid.value = null;
isDuplicate.value = null;
error.value = null;
return;
}
debouncedCheck(v);
});
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,
};
}
```
# 4) 컴포넌트: 입력 UI (components/SignUpUsernameField.vue)
```vue
<!-- components/SignUpUsernameField.vue -->
<script setup>
import { computed } from 'vue';
import { useUsernameCheck } from '@/composables/useUsernameCheck';
const {
username,
isFormatValid,
isChecking,
isDuplicate,
error,
checkNow,
canSubmit,
} = useUsernameCheck();
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 '';
});
</script>
<template>
<div class="field">
<label for="username">아이디</label>
<div class="row">
<input
id="username"
v-model.trim="username"
type="text"
placeholder="아이디를 입력하세요"
autocomplete="username"
inputmode="latin"
/>
<button type="button" @click="checkNow" :disabled="!username || isChecking">
중복 확인
</button>
</div>
<p
class="helper"
:class="{
error: isFormatValid === false || isDuplicate === true || !!error,
success: isDuplicate === false
}"
>
{{ helperText }}
</p>
<button type="submit" :disabled="!canSubmit">회원가입</button>
</div>
</template>
<style scoped>
.field { display: grid; gap: 8px; }
.row { display: flex; gap: 8px; align-items: center; }
.helper { font-size: 12px; margin: 0; }
.helper.error { color: #d32f2f; }
.helper.success { color: #2e7d32; }
</style>
```
# 5) 서버 예시: Node/Express (server.js)
TypeScript, zod 제거 버전으로 간단한 검증만 JS로 처리합니다.
```js
// server.js
import express from 'express';
const app = express();
app.use(express.json());
// 매우 단순한 형식 검증(프런트와 동일한 정규식)
const USERNAME_REGEX = /^[a-z0-9_]{4,16}$/;
// 예시: 이미 존재하는 아이디 조회 함수(실서비스에서는 DB 사용)
async function isUsernameTaken(username) {
// TODO: DB 조회로 교체
const taken = new Set(['admin', 'testuser', 'guest']);
return taken.has(username);
}
app.post('/api/auth/check-username', async (req, res) => {
const { username } = req.body || {};
if (typeof username !== 'string' || !USERNAME_REGEX.test(username)) {
return res.status(400).json({ message: 'invalid username' });
}
try {
const exists = await isUsernameTaken(username);
return res.json({ exists });
} catch (e) {
return res.status(500).json({ message: 'internal error' });
}
});
app.listen(3000, () => {
console.log('Server started on http://localhost:3000');
});
```
# 메모
- import 경로 별칭(@/...)은 Vite/webpack 설정에 따라 다릅니다. 별칭을 쓰지 않는다면 상대경로로 바꾸세요.
- 타입 제거 외에는 로직 변경이 없습니다. ESM이 아닌 CommonJS 환경이라면 import/export 대신 require/module.exports를 사용하세요.
- 테스트 환경(Vitest/Jest)도 타입 정의 없이 그대로 동작하도록 설정 가능합니다. 필요하면 예제 추가해 드리겠습니다.
출처
카테고리 없음
vue3 에서 회원가입 기능을 만들건데 입력한 아이디 정보가 이미 등록된 아이디인지 체크하는 기능을 구현해줘
반응형
반응형