로그인 기능을 구현하면서 DB 연동까지 마친 상태라 회원 가입을 구현하는 것도 금방 끝낼줄 알았다.
회원가입의 필수 기능 중 하나인 '중복 확인' 기능을 동기 방식으로 구현했다면 진작에 완성했겠지만 이왕이면 비동기 방식으로 구현하고싶었다.
동기 방식 VS 비동기 방식
우선 동기 방식과 비동기 방식의 차이점은 중복 확인 절차가 사용자의 입력이 끝난 시점에 진행하는지 여부에 있다.
쉽게 말해 '중복 확인 버튼'이 있을 경우, 사용자가 입력을 마치고 버튼을 눌러야 중복 확인 절차가 진행되므로 동기 방식으로 처리된다고 볼 수 있다.

반대로 비동기 방식의 경우 사용자가 입력을 하는 동안 서버에서 입력값을 실시간으로 감지해 중복 확인 절차가 진행되는 것을 말한다.
최근 만들어진 사이트는 대부분 비동기 방식을 채택했을 것이다.

구현 과정 중...
매커니즘 자체는 딱히 복잡할게 없다.
클라이언트에서 useState를 통해 실시간으로 입력을 받고, 그것을 그대로 서버에 넘겨 DB값과 대조한 다음 결과를 다시 받아오면 된다.
하지만 구현 중 다음 문제점들을 맞이했고 이를 해결하는데 생각보다 시간이 오래 걸렸다.
- 입력을 실시간으로 넘기다보니 서버에 불필요한 통신 과정이 생김
- 최신 state 값이 반영되지 않음
- 여러개의 input에 중복값이 들어갈 경우 서버 오류가 발생함
개발에 어느정도 익숙한 사람이라면 애초에 고민거리도 아니라 할 수도 있지만.. 초보 입장에선 너무 답이 안보였다. 😂
그래서 이번 포스팅은 문제점과 해결 과정 위주로 다룰 예정이다.

불필요한 통신 줄이기 ▶ lodash 라이브러리 사용
만약 사용자의 입력이 있을 때마다 서버와 통신을 한다면 'newbee1234' 라는 ID를 입력할 경우 문자열의 길이만큼 총 10번의 통신이 이루어진다.
개인 프로젝트 수준에서는 이정도로 문제가 생기진 않겠지만, 실제로 배포하는 서비스라면 필요 이상으로 서버 유지 비용이 들어갈 것이므로 반드시 개선이 필요한 방식이다.
따라서 불필요한 api 호출 횟수를 줄일 방법으로 lodash 라이브러리를 사용하기로 했다.
lodash는 함수의 호출 속도를 제한하는 debounce와 호출 횟수를 제한하는 throttle 기능을 제공한다.
개인적으로 사용자의 텍스트 입력 감지에는 debounce가 적합하다고 판단해 이 방식으로 구현했다.
# lodash 설치
npm i lodash
//Register.jsx
import { useState, useCallback } from 'react';
import { debounce } from 'lodash';
import axios from 'axios';
const Register()=>{
// 회원가입 폼 input, 중복 체크 여부 초기값
const initReg=useRef({regId:'', regPw:'', regPwChk:'', regNickname:'', regEmail:'', regCar:''});
const initDup=useRef({dupId:false, dupNickname:false, dupEmail:false});
// 회원가입 폼 입력, 중복 여부 판별 state
const [inputReg, setInputReg]=useState(initReg);
const [dupState, setDupState]=useState(initDup);
// 폼 입력 시 지나치게 많은 api 호출을 방지(debounce)
const debounceInputReg=useCallback(debounce((input)=>{
// 회원가입 api 호출
axios.post('/api/auth/register', {'registerValue':input})
.then((res)=>{
setDupState({
dupId:res.data.idExist,
dupNickname:res.data.nicknameExist,
dupEmail:res.data.emailExist
});
}).catch();
}, 300),[]); // 함수 재생성 방지(useCallback)
return (
<form>
<input id="user_id" name="regId" type="text" placeholder="사용할 ID를 입력하세요." value={inputReg.regId} onChange={onChangeInput} />
<!-- 다른 input 생략 -->
<button type="button" onClick={onClickInit}>초기화</button>
<button type="submit" onClick={onClickRegister}>가입하기</button>
</form>
);
}
export default Register;
우선 사용자의 입력을 받을 inputReg와 입력값이 DB에 존재하는지 확인하고 중복 여부를 관리할 dupState를 useState로 만들어주었다.
각각의 초기값은 모두 공백이거나 false인 상태이다.
debounceInputReg
api 호출을 제어할 debounceInputReg 함수를 useCallback Hook과 함께 선언했다.
debounce 함수의 지연시간은 300으로 지정했으며, 이는 사용자의 마지막 입력이 감지된 시점에서 0.3초가 지난 뒤 서버와 통신을 한다는 의미이다.
이후 서버에서 보낸 결과값에 따라 exist 상태의 idExist, nicknameExist, emailExist 속성 중 중복으로 입력된 항목을 true로 변경해준다.
최신 상태의 입력값 전달 ▶ onChange 함수 안에 새로운 변수 생성
onChangeInput
원래는 setInputReg에 바로 변경된 상태값을 입력해 inputReg를 수정하도록 코드를 짰다.
그런데 리액트의 상태관리 방식과 debounce 함수의 충돌이 발생하는지 서버에 마지막 입력 상태가 아닌 마지막 직전 상태까지만 전송이 되는 문제가 있었다.
즉, 'abcd'를 입력하면 'abc' 까지만 전송이 되었다.
// Resigter.jsx
const onChangeInput=(e)=>{
// debounce 함수 호출 시 최신 상태가 정확히 반영이 되지 않아 newInputReg 생성 후 상태 업데이트
const newInputReg={
...inputReg,
[e.target.name]:e.target.value
}
setInputReg(newInputReg);
debounceInputReg(newInputReg);
}
명확한 원리는 아직 파악하지 못했지만 onChangeInput 함수 안에서 새로운 변수 newInputReg를 선언하고 이것을 debounceInputReg의 파라미터로 전달했더니 서버에도 정상적인 입력값이 전달되었다.
이 현상은 추후에 리액트 상태관리의 동작 방식과 함께 좀 더 분석을 해보면 좋을 것 같다.
여러 input의 중복값 확인 ▶ Promise.all() 매서드 활용
이 부분은 사용자가 입력한 ID, 닉네임, 이메일 주소 중 두 개 이상의 항목이 중복된 항목일 경우 서버 오류가 발생한 상황의 해결법이다.
이 부분은 백엔드의 영역이기 때문에 별도의 포스팅으로 분리했다.
https://duski96.tistory.com/19
[node.js] Promise.all() 매서드 사용하기
회원 가입 폼을 만들던 중 계속해서 서버가 종료되는 오류가 발생했다.처음엔 중복 확인이 필요한 항목이 세 개니까 query() 매서드 세 개를 써서 각각 응답을 보내면 되겠다고 생각했다.하지만
duski96.tistory.com
입력 제출
사용자가 '가입하기' 버튼을 누른다면 <button type="submit" /> 에 해당하는 동작이 수행되어야 한다.
하지만 HTML에 내장된 submit 기능은 페이지를 새로 로드하기 때문에 리액트에선 쓰지 않고 별도의 api를 호출해 해당 기능을 대신할 것이다.
// Register.jsx
const onClickRegister=(e)=>{
// HTML 버튼의 기본 submit 기능 방지
e.preventDefault();
// 입력한 정보 중 중복 항목이 있으면 즉시 종료
if(Object.values(dupState).includes(true)){
alert('중복된 항목을 확인하세요.');
return;
}
// 비어있는 입력 항목이 있으면 즉시 종료
if(Object.values(inputReg).includes('')){
alert('입력되지 않은 항목을 확인하세요.');
return;
}
// 비밀번호 체크가 되지 않았을 경우 즉시 종료
if(inputReg.regPw!==inputReg.regPwChk){
alert('패스워드를 체크하세요.');
return;
}
axios.post('/api/auth/register_submit', {'registerSubmit':inputReg})
.then(()=>{
nav('/login', {replace:true});
alert('회원가입이 완료되었습니다. 로그인이 필요합니다.');
}).catch(()=>{
nav('/login', {replace:true});
alert('오류가 발생했습니다.');
});
}
가입하기 버튼을 누르면 register_submit api를 호출하고, 입력한 데이터는 패스워드를 포함하기 때문에 POST 방식으로 전달한다.
이때 중복 항목을 관리하던 dupState 상태 변수에 true 값이 존재하거나 항목을 아예 입력하지 않은 경우, 또한 패스워드 재확인 과정을 거치지 않은 경우 api 호출 전에 함수가 종료되도록 만들었다.
참고로 패스워드 재확인 코드는 단순 상태비교이기 때문에 따로 포스팅에 추가하진 않았다.

제출 가능한 모든 조건이 갖춰졌다면 제출 api를 통해 DB에 실제 회원 정보가 추가될 것이다.
이 과정 또한 백엔드에서 진행되기에 별도의 포스팅을 참고하면 된다.
https://duski96.tistory.com/20
[Node.js] DB에 입력 값 추가하기
홈페이지 방문자가 회원 가입을 진행한다면 입력한 데이터는 DB에 저장된다.이번 포스팅은 DB에서 데이터를 불러오지 않고 반대로 데이터를 추가하는 방법을 다룰 것이다.사실 이 부분은 SQL문만
duski96.tistory.com
'React' 카테고리의 다른 글
[React] Vercel에서 리액트 앱 배포하기 (0) | 2025.03.19 |
---|---|
[React] 게시판 구현하기 (0) | 2025.03.14 |
[React] 로그인 구현하기 (0) | 2025.03.02 |
[React] React에 MySQL 연동하기 (0) | 2025.02.25 |
[React] React에서 Fancybox 사용하기 (0) | 2025.02.20 |