로그인 기능 구현하기 - 유효성 검사 및 애니메이션

ChoiYongHyeun·2024년 1월 22일
0

망가뜨린 장난감들

목록 보기
12/19
post-thumbnail

저번 회차 때 디자인을 끝내 놓았으니 이제 기능 할 수 있도록 유효성 검사를 해야겠다.

로그인 기능 구현하기 - 목표 설정 및 디자인

어제 밤까지만 해도 유효성 검사를 백엔드 단에서 해야하나 .. 프론트 단에서 해야하나 .. 고민했는데

프론트 단에서 하는게 좋은 것 같다.

그 이유를 서칭을 통해 알 수 있었는데

  • 클라이언트 사이드 유효성 검사는 사용자에게 즉각적인 피드백을 제공할 수 있어 사용자 경험 향상
  • 서버에게 유효한 POST 요청만 보냄으로서 서버 부하 감소
  • 아이디와 비밀번호와 같이 보안이 필요한 정보의 전송은 최소화 하는 것이 좋음

등에 이유로 클라이언트 사이드 유효성 검사를 하기로 마음 먹었다.

어제 밤에 이런 고민을 했던 것은
"아니 회원 가입 할 때 유효성 검사 해서 가입 시켜두면, 그냥 날리는 POST 요청마다 데이터베이스에서 찾기만 하면 되는거 아닌가 .. ?"
이런 생각을 했었다.

근데 시간 지나고 생각해보니 모든 요청마다 데이터베이스에서 해당 회원이 존재하는지 확인하는 것 자체가 비용이니까 프론트단에서 해결하는게 맞는 것 같다.


유효성 검사 기준 세우기

이메일과 비밀번호에 대한 유효성 검사를 하기 위해 기준을 세워보자

이메일 유효성 검사

이메일의 유효성 검사 표준이 있더라 !

RFC 5322

하지만 해당 페이지만 보고 뭘 의미하는지 정확히 알 수 없어 웹에서 서칭해왔다.

정규식(Regex)으로 이메일 주소를 확인하는 방법. 샘플 HTML5, PHP, C#, Python 및 Java 코드.

인터넷 메시지 형식은 로컬 부분도메인 부분 으로 나뉜다.

example123@naver.com 이란 이메일 주소가 있을 때 example123 은 로컬 부분 , naver.com 은 도메인 부분이다.

로컬 부분은 영숫자 문자, 마침표, 하이픈, 더하기 기호 및 밑줄의 조합이 포함 될 수 있으며 도메인 부분은 도메인 이름 (naver) 과 최상위 도메인 (TLD (Top-Level-Domain) , 여기서는com) 으로 구성되어 있다.

TLD 는 일반적인 목적으로 사용되는 도메인인 gTLDs (Generic-Top-Level-Domains)ccTLDs(Country Code Top-Level Domains) 로 나뉜다.

TLD Type Examples
Generic Top-Level Domains (gTLDs) .com, .org, .net, .edu, .gov, .mil, .int
Country Code Top-Level Domains (ccTLDs) .us, .uk, .de, .jp, .kr, .cn

이메일 주소 정규표현식

이메일 주소 표준화는 생각보다 훨씬 복잡하다

([!#-'*+/-9=?A-Z^-~-]+(\.[!#-'*+/-9=?A-Z^-~-]+)*|\"\(\[\]!#-[^-~ \t]|(\\[\t -~]))+\")
@
([!#-'*+/-9=?A-Z^-~-]+(\.[!#-'*+/-9=?A-Z^-~-]+)*|\[[\t -Z^-~]*])

우웨에에엑

이 부분을 해석해서 해보려고 했는데 아직 정규표현식 짬이 부족하다.
일단 이대로 쓰고 넘어가는걸로 하자
RFC5322 에 정의되어 99.9% 의 이메일들을 모두 잡을 수 있다고 한다.

const regex = new RegExp(
  "([!#-'*+/-9=?A-Z^-~-]+(\\.[!#-'*+/-9=?A-Z^-~-]+)*|\"\\(\\[\\]!#-[^-~ \\t]|(\\\\[\\t -~]))+@([!#-'*+/-9=?A-Z^-~-]+(\\.[!#-'*+/-9=?A-Z^-~-]+)*|\\[[\\t -Z^-~]*])",
);

const emails = ['wrong.email', 'example1@naver.com', 'example2@korea.co.kr'];

emails.forEach((e) => {
  console.log(`${e} : ${regex.test(e)}`);
});

/**
wrong.email : false
example1@naver.com : true
example2@korea.co.kr : true
*/

해당 정규표현식을 정규표현식 객체를 만들 때 \ 기호를 인식시키기 위해 정규표현식의 실제\ 앞에 \ 를 하나 더 붙여주었다.

비밀번호 정규 표현식

  /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]{8,10}/;

기준은 하나 이상의 영어 대문자 , 하나 이상의 특수기호 , 하나 이상의 숫자가 들어가며 8글자와 13글자 사이의 비밀번호를 검증하는 정규 표현식을 가져왔다.

테스트 해보자

const regex =
  /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]{8,13}/;

const passwords = ['wrongpassword', 'Password1!', 'pASSWORD@!1'];

passwords.forEach((p) => {
  console.log(`${p} : ${regex.test(p)}`);
});
/**
wrongpassword : false
Password1! : true
pASSWORD@!1 : true
*/

구우우웃

아 정규표현식 너무 어렵다


프론트 단에서 구성해야 하는 기능

아직 백엔드 하시는분은 존재하지 않지만 가상의 백엔드 팀원을 상상하면서 코드를 작성해봤다.

백엔드 팀원님 : "프론트님 , 로그인 요청 보내기 전에 클라이언트 단에서 유효성 검사 해주시고 맞지 않으면 POST 보내지 말아주세요 , 아 그리고 맞지 않을 시 로그인 화면이 동적으로 변했으면 좋겠어요"

호다닥 호다닥

유효성 검사 객체 생성

class Validator {
  constructor() {
    this.emailRegex = new RegExp(
      "^([!#-'*+/-9=?A-Z^-~-]+(\\.[!#-'*+/-9=?A-Z^-~-]+)*|\"\\(\\[\\]!#-[^-~ \\t]|(\\\\[\\t -~]))+@([!#-'*+/-9=?A-Z^-~-]+(\\.[!#-'*+/-9=?A-Z^-~-]+)*|\\[[\\t -Z^-~]*])$",
    );

    this.passwordRegex = new RegExp(
      '^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[$@$!%*?&])[A-Za-z\\d$@$!%*?&]{8,13}$',
    );
  }

  checkEmpty(email, password) {
    return email.length + password.length !== 0;
  }

  checkEmail(email) {
    return this.emailRegex.test(email);
  }

  checkPassword(password) {
    return this.passwordRegex.test(password);
  }
}

이메일과 비밀번호를 검사하는 정규표현식 객체와 검사하는 메소드들이 담긴 Validator 생성자를 생성해준다.

애니메이션 기능 구현

원래는 경고창을 alert 으로 띄우려고 했는데 진짜 너무 못생김

가장 보편적으로 오류가 났음을 나타낼 수 있는 방법은 로그인창이 흔들리는 거라 생각했다.

근데 흔들리기만 하면 재미없으니까 저번 회차에서 배웠던 backdrop-filter 를 사용해보자

.wrong {
  backdrop-filter: blur(20px) contrast(200%);
  animation: horiznotalMove 0.1s 5 alternate;
}

@keyframes horiznotalMove {
  from {
    left: -10px;
  }
  to {
    left: 10px;
  }
}

wrong 이란 클래스를 가진 태그는 배경색이 투명해지는 것 뿐이 아니라 contrast 값을 올려줘서 강조해주도록 하자

그리고 흔들리기만 하면 뭐가 문제인지 모르니까 텍스트로 문제점을 넣어주도록 하자

	.
	.
            type="password"
            autocomplete="off"
            required
          />
        </div>
        <div class="custom-checkbox">
          <input type="checkbox" name="remember" id="remember" />
          <label for="remember">Remember me</label>
        </div>
      </form>
      <button id="login-button">Login</button>
      <div class="footer-text">
        Don't have an account? <a href="">Create One</a>
      </div>
      <div class="error hidden">비밀번호를 확인해주세요</div>
    </div> 
	.
	.
	.
.error {
  text-align: center;
  font-size: 20px;
  color: red;
  opacity: 1;
  transition: all 0.2s ease;
}

.hidden {
  opacity: 0;
}

가장 하단 부위에 div 태그를 생성해주고 클래스로 errorhidden 을 넣어준다.

이후 유효성 검사에 통과하지 못했을 경우 로그인 창에는 wrong 이란 클래스를 추가해줬다가 빼고 error 태그에는 문제점을 적고 hidden 이란 클래스를 제거했다가 넣어주는 함수를 만들자

const Tags = {
  $container: document.querySelector('.container'),
  $error: document.querySelector('.error'),
};

const wrongInput = (text) => {
  const DELAY = 1000;
  const { $container, $error } = Tags;

  setTimeout(() => {
    $container.classList.remove('wrong');
    $error.classList.add('hidden');
  }, DELAY);
  $container.classList.add('wrong');

  $error.textContent = text;
  $error.classList.remove('hidden');
};

setTImeout 을 이용해서 DELAY 시간 이후에는 원상 복구 시키고 DELAY 시간 전까지는 로그인 박스가 흔들리도록 하는 클래스, 에러창이 눈에 보이게 만들어주었다.

조건에 따라 로그인 박스 동적으로 변경하기

const formChild = {
  $email: document.querySelector('#email'),
  $password: document.querySelector('#password'),
  $remember: document.querySelector('#remember'),
};
const validator = new Validator();
const $loginButton = document.querySelector('#login-button');

$loginButton.addEventListener('click', () => {
  const { $email, $password, $remember } = formChild;
  const [email, password, remember] = [
    $email.value,
    $password.value,
    $remember.checked,
  ];


  if (!validator.checkEmpty(email, password)) {
    wrongInput('아이디와 비밀번호를 모두 입력해주세요');
    return;
  }
  if (!validator.checkEmail(email)) {
    wrongInput('이메일 형식을 다시 확인해주세요');
    return;
  }
  if (!validator.checkPassword(password)) {
    wrongInput('비밀번호 형식을 다시 확인해주세요');
    return;
  }
  console.log('POST 요청을 보내는중~');
  console.log(email, password, remember);
});

그래서 조건에 따라 클래스명과 에러 텍스트를 변경해나가도록 만들었다.

잘 되나 확인해보자

구우우웃~~~


아 참

이전 포스트에서는 Login 버튼을 input type = "submit" 으로 했었는데 button 으로 변경해버렸다.

그 이유는 input type=submit 으로 해버리면 form 의 기본 이벤트인 제출 시 폼이 초기화 되는 문제가 있었다.

그래서 preventDefault 를 써보려고 요리조리 만져보다가 그냥 버튼으로 만들어버렸다.


회고

express 를 이용해서 서버를 만들어서 POST 를 이용해 로그인을 하기 위한 소통까지는 구현했었다.

그런데 이렇게 하다보니 로그인 이후의 페이지를 어떻게 렌더링 시킬지 ?

를 생각하다보니 세션과 쿠키에 대한 공부를 하게 되고

그렇게 하다 답이 안나와 다른 사람들의 코드들을 깃허브에서 엄청 둘러보았다.

보다보니 느낀 점은

내가 혼자 독학으로 하는 것보다 웰메이드 된 코드들을 봐야겠다!!! 라고 생각이 들었다.

내 혼자 스스로 생각한 컴포넌트라는 개념은 실제로 달랐다.

아~ 나는 리액트가 아니라 그래 ~!

이렇게 생각했는데 어떤 분이 바닐라 자바스크립트로 컴포넌트들을 구성한걸 보았다.
와우
m's blog

그래서 일단 로그인 페이지 공부는 여기까지만 하고

다른 사람들의 웰메이드 코드들도 공부하고

어떻게 백엔드와 협업하여 API 를 이용하여 렌더링 하는지 , 세션과 토큰은 어떻게 하고 헤더는 어떻게 날리는지에 대해 더 공부해봐야겠다고 생각했다.

코드만 주구장창 쓰는 것보다 시간을 줄여서 다른 사람들이 이미 개척해나간 길들을 따라

공부해야겠다.

바퀴를 두 번 발명하지 말라는 말이 있었으니까 ..

그리고 느낀점은 많은 모듈들을 다뤄봐야 할 것 같다. 기술 블로그들을 보니 사용하는 모듈들이 최소 5개씩은 넘더라

profile
빨리 가는 유일한 방법은 제대로 가는 것이다

0개의 댓글