프로그래머스 스쿨이 제공하는 과제 테스트 예제를 풀어보다가, 로그인 화면 구현을 실습했다. 그리고 작업 폴더 속에서 여러개의 JS 파일을 만들고, 그걸 모듈로 이용해 좀 더 깔끔하게 작업을 할 수 있는 방법을 공부했다.
index.html
main.js
package.json
src/
main.css
app.js
components/
login.js
이런 식의 작업폴더 구조로 시작했다. 구현하고자 하는 기능에는 아직 회원가입은 없었기 때문에, 로그인 버튼 클릭 시 이벤트를 발생 시키고, 그 이벤트에 적절한 조건을 갖춘 아이디와 패스워드가 삽입 되었는 지 검토하는 코드를 만들면 되는 터였다. html 파일의 id를 주요 인자로 사용하여 아주 간단한 형태의 js 코드들을 가이드에 따라 작성하며 이해해 보았다.
1. 단정해진 html
js 파일은 script type="module"로 연결, css 파일은 link태그로 연결했다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="favicon.ico" type="image/x-icon" />
<title>간단한 로그인 화면</title>
<link rel="stylesheet" type="text/css" href="./src/main.css" />
</head>
<body>
<div class="login-container">
<h2>로그인</h2>
<input
id="username"
class="input-field"
type="text"
placeholder="사용자 이름"
/>
<input
id="password"
class="input-field"
type="password"
placeholder="비밀번호"
/>
<button id="loginButton" class="login-button">로그인</button>
</div>
<script type="module" src="main.js"></script>
</body>
</html>
2. main.js
main.js는 html과 연결하여, 다른 모든 기능별 js.파일을 import해 붙여줄 수 있는
물리적 공간을 형성한다. 프로젝트의 유지보수에 무척 좋다는 걸 깨닳았다.
/* 구현하고자 하는 각 기능을 이렇게 분리하여 작성한 것을 모듈이라고 하고,
같은 JS 파일 내부에서 불러내어 사용할 수 있다. 코드의 가독성을 올린다 */
import App from "./src/app.js";
new App(document.querySelector("body"));
3. app.js
this. 는 함수를 호출하는 주체에 따라 다른 값을 가지는 동적인 성격을 가지고 있다.
import Login from "./components/login.js";
// 클래스 App를 선언
class App {
/* constructor 함수를 선언, 쿼리셀렉터로 id, pw, button의 id를 지정
JS에서 html에 관여할 수 있게 시동을 걸어주는 메소드*/
constructor() {
// this.는 함수를 호출한 주체에 따라 동적으로 다른 값을 가진다
this.emailInput = document.querySelector("#username");
this.passwordInput = document.querySelector("#password");
this.theaterLoginBtn = document.querySelector("#loginButton");
this.render();
}
handleLoginClick = () => {
new Login(this.emailInput, this.passwordInput);
};
// 아래 컴포넌트가 그리는 UI를 생성하고 반환하는 render()
// 로그인 버튼을 click하면 handleLoginClick 함수를 작동한다
render() {
this.theaterLoginBtn.addEventListener("click", this.handleLoginClick);
}
}
export default App;
4. login.js
login -> app -> main -> html 순서로 각 기능을 구현한 모듈들이 클라이언트로 직접 관여하며 기능을 완성하고 있다. 그동안은 이 모든걸 하나의 js파일에서 작업했었는데, 이렇게 작은 프로젝트에서는 그게 못 할 일은 아니지만, 이런 방식이 가진 장점에 대해 크게 깨닳았다.
// Login 클래스 생성
class Login {
// 생성자는 html에서 받는 id와 pw를 매개변수로 실행된다
constructor(emailInput, passwordInput) {
this.emailInput = emailInput;
this.passwordInput = passwordInput;
this.render();
}
// 입력값들이 공백으로 인해 에러가 나는 것을 방지하고자, trim()으로 공백을 지워주는 단
checkRequiredValueIsEnteredInField() {
return [this.emailInput, this.passwordInput].every(
(input) => input.value.trim() !== ""
);
}
// id가 요구조건을 충족하는 지 검토하는 함수
checkEmailFormat() {
const re = /^[a-zA-Z0-9\.]+@[a-z0-9-_\.]+\.co$/;
const email = this.emailInput.value;
return re.test(email.trim());
}
// pw가 조건에 부합하는 길이와 요소를 포함하고 있는 지 검토하는 두 함수
checkPasswordInputLength(minLength, maxLength) {
const passwordLength = this.passwordInput.value.length;
return !(passwordLength < minLength || passwordLength > maxLength);
}
checkPasswordCombinationValidation() {
const re = /^(?=.*[a-zA-Z])(?=.*[!@~])(?=.*[0-9])[a-zA-Z0-9!@~]{8,20}$/;
const password = this.passwordInput.value;
return re.test(password);
}
// render()는 구현된 부분들을 클라이언트에서 볼 수 있게 그려준다
render() {
// 유효성 검사는 배열인데, 객체와 함수를 내포하고 있다
const validations = [
{
fn: this.checkRequiredValueIsEnteredInField,
errMsg: "아이디 혹은 비밀번호가 입력되지 않았습니다.",
},
];
for (let validation of validations) {
if (!validation.fn.call(this)) {
alert(validation.errMsg);
return;
}
}
alert("로그인 성공!");
location.reload();
}
}
export default Login;
개발자로서 배울 점이 많은 글이었습니다. 감사합니다.