회원가입 기능을 구현한다.
회원 가입 기능을 구현하기 위해서는 Passowrd 필드 아래에 입력한 Password가 맞는지 한번 더 확인하는 필드가 추가로 필요하다. v-model 또한 script에서 data로 지정이 필요하다.
Password 와 동일하게 생성하되 v-model만 다르게 지정한다.
script의 data에 passwordVerify를 생성한다.
script는 Vuetify 참고 (Forms)
<v-text-field
v-model="password"
:readonly="loading"
:rules="[required]"
clearable
label="Password"
placeholder="Enter your password"
></v-text-field>
<v-text-field
v-model="passwordVerify"
:readonly="loading"
:rules="[required]"
clearable
label="Password"
placeholder="Enter your password"
></v-text-field>
<script>
export default {
data: () => ({
form: false,
email: null,
password: null,
passwordVerify: null,
loading: false,
}),
methods: {
onSubmit () {
if (!this.form) return
this.loading = true
setTimeout(() => (this.loading = false), 2000)
},
required (v) {
return !!v || 'Field is required'
},
},
}
</script>
rules를 만들어서 실시간 검증이 되도록 적용한다.
duplicateCheck의 경우에는 emailDuplicatecheck 메소드를 생성해서 Server로 데이터를 전송하고 리턴값을 받아서 처리해야 한다. Server와 연동 후 구현 예정이라 임시적으로 true를 리턴하도록 한다.
<v-text-field
v-model="email"
:readonly="loading"
:rules="[rules.required, rules.email.expertTest, rules.email.emailDuplicateCheck]"
class="mb-2"
clearable
label="Email"
></v-text-field>
<script>
export default {
data: () => ({
form: false,
email: null,
password: null,
passwordVerify: null,
loading: false,
rules: {
email: {
expertTest: v => {
const emailExpert = /^[A-Za-z0-9_\\.\\-]+@[A-Za-z0-9\\-]+\.[A-Za-z0-9\\-]+/
return !!emailExpert.test(v) || 'It must be in email format.'
},
duplicateCheck: v => {
return this.emailDuplicateCheck(v) || 'This is a duplicate email. Please use a different email.'
}
},
required: v => !!v || 'Field is required'
},
}),
methods: {
onSubmit () {
if (!this.form) return
this.loading = true
setTimeout(() => (this.loading = false), 2000)
},
emailDuplicateCheck (email) {
console.log(email)
return true;
}
},
}
</script>
위 내용에 따르면 rules를 이용해 실시간 검증 방식을 사용하게 되면 Server와 통신하면서 제대로 반영이 안되는 문제가 있는 것으로 판단된다. 이메일 중복 검사의 경우 버튼을 생성해 처리하던가 중복과 관련된 상태값을 추가로 설정해서 판별 후 화면에 출력하는 형태로 바꾸어야 할 거 같다.
Password 규칙은 KISA 패스워드 선택 및 이용 안내서를 따른다.
https://www.kisa.or.kr/2060305/form?postSeq=14&page=1#fnPostAttachDownload
패스워드 생성 규칙만 정리하면 다음과 같다.
1. 패스워드 최소 8자 이상
2. 영문, 숫자, 특수 기호를 조합하여 사용할 수 있도록 허용
위 규칙으로만은 모호한 부분이 있어서 추가로 정보를 찾은 결과 개인정보의기술적관리적보호조치기준(제2020-5호)_해설서(2020.12월)를 참고 했다.
https://www.privacy.go.kr/cmm/fms/FileDown.do?atchFileId=FILE_000000000841151&fileSn=0
정리하면 다음과 같다.
이 규칙을 참고해서 영문, 숫자, 특수문자 중 3종류 이상 조합 최소 8자리 rules를 지정한다.
<v-text-field
v-model="password"
:readonly="loading"
:rules="[rules.required, rules.passowrd.expertTest, rules.passowrd.lengthCheck]"
clearable
label="Password"
placeholder="Enter your password"
></v-text-field>
passowrd: {
expertTest: v => {
const passwordExpert = /^(?=(?:[^a-zA-Z]*[a-zA-Z]))(?=(?:\D*\d))(?=(?:[^\W_]*[\W_])).{8,}$/
return !!passwordExpert.test(v) || 'It must be at least 8 characters and a combination of 3 or more letters, numbers, and special characters.'
},
lengthCheck: v => {
return v.length < 50 || 'The password you entered is too long.'
}
},
정규식을 사용해서 8자리 이상, 영문, 숫자, 특수문자 중 3종류 이상 조합 되도록 규칙을 구성했다. 그리고 너무 긴 문자열 입력을 방지하기 위해 lengthCheck를 적용했다.(email 부분에도 적용했다.)
Script
passwordVerify: {
passwordCheck: v => {
return this.passwordMatchCheck(v) || 'It does not match your password.'
}
},
...
passwordMatchCheck (passwordVerify) {
return (this.password === passwordVerify)
},
Text-field
<v-text-field
v-model="passwordVerify"
:readonly="loading"
:rules="[rules.required, rules.passwordVerify.passwordCheck]"
clearable
label="Password"
placeholder="Enter your password"
></v-text-field>
arrow function에서 this는 사용이 안되는 것을 알고 있었는데 email 형식검사에서 에러 발생 안되서 의아해 했는데 확인해보니 rules로 지정한 것과 실제 지정된 rules의 이름이 달라서 매칭이 안되고 있었던 것...
Passowrd Verify 위 코드 적용시 Cannot read property of undefined가 발생했다.
arrow function에서는 this가 안먹히므로 arrow function 형태를 바꿔준다.
...
단순히 arrow function을 사용하지 않고 this.~ 형태로 적용하면 된다고 생각했지만... undefined 에러가 계속 발생한다... 여러 다양한 시행착오 끝에 최종적으로 rules에 원하는 methods를 바로 지정하면 되는 것을 알게됬다.
:rules="[rules.required, rules.email.expertTest, emailDuplicateCheck, rules.email.lengthCheck]"
emailDuplicateCheck라는 methods를 만들고 rules에 바로 지정하면 된다.
emailDuplicateCheck (email) {
console.log(email)
return true;
},
Password Verify도 Script에 methods를 추가하고
passwordMatchCheck (passwordVerify) {
return (this.password === passwordVerify) || 'It does not match your password.'
},
rules 부분에 생성한 method를 넣어준다.
<v-text-field
v-model="passwordVerify"
:readonly="loading"
:rules="[rules.required, passwordMatchCheck]"
clearable
label="Password"
placeholder="Enter your password"
></v-text-field>
테스트 결과 전부 정상적으로 처리되는것을 확인했다.
password 타입으로 지정해서 노출되는것을 방지한다.
<v-text-field
v-model="password"
:readonly="loading"
:rules="[rules.required, rules.passowrd.expertTest, rules.passowrd.lengthCheck]"
type="password"
clearable
label="Password"
placeholder="Enter your password"
></v-text-field>
<v-text-field
v-model="passwordVerify"
:readonly="loading"
:rules="[rules.required, passwordMatchCheck]"
type="password"
clearable
label="Password"
placeholder="Enter your password"
></v-text-field>
양식이 모두 활성화가 되었다면 Form 상태를 변경해서 SIGN UP 버튼이 활성화 되도록 변경한다.
v-form은 다음과 같이 v-model이 "form"으로 바인딩 되어 있다. 이는 rules와 연동되어 모든 조건을 만족하게 된다면 form 값도 바뀌게 된다. 따라서 별도의 코드 수정은 필요 없다.
<v-form
v-model="form"
@submit.prevent="onSubmit"
>
Fetch API와 Axios를 통해 HTTP 요청을 보낼 수 있다. 두 개를 비교하는데 상당히 길어질거 같기도 하므로 다음 글에서 작성!
SignUpPage.vue
<template>
<v-sheet class="pa-12" rounded>
<v-card class="mx-auto px-6 py-8" max-width="60%" :elevation="12">
<v-form
v-model="form"
@submit.prevent="onSubmit"
>
<v-text-field
v-model="email"
:readonly="loading"
:rules="[rules.required, rules.email.expertTest, emailDuplicateCheck, rules.email.lengthCheck]"
class="mb-2"
clearable
label="Email"
></v-text-field>
<v-text-field
v-model="password"
:readonly="loading"
:rules="[rules.required, rules.passowrd.expertTest, rules.passowrd.lengthCheck]"
type="password"
clearable
label="Password"
placeholder="Enter your password"
></v-text-field>
<v-text-field
v-model="passwordVerify"
:readonly="loading"
:rules="[rules.required, passwordMatchCheck]"
type="password"
clearable
label="Password"
placeholder="Enter your password"
></v-text-field>
<br>
<v-btn
:disabled="!form"
:loading="loading"
block
color="primary"
size="large"
type="submit"
variant="elevated"
>
Sign Up
</v-btn>
</v-form>
</v-card>
</v-sheet>
</template>
<script>
export default {
data: () => ({
form: false,
email: null,
password: null,
passwordVerify: null,
loading: false,
rules: {
email: {
expertTest: v => {
const emailExpert = /^[A-Za-z0-9_\\.\\-]+@[A-Za-z0-9\\-]+\.[A-Za-z0-9\\-]+/
return !!emailExpert.test(v) || 'It must be in email format.'
},
lengthCheck: v => {
return v.length < 50 || 'The email you entered is too long.'
}
},
passowrd: {
expertTest: v => {
const passwordExpert = /^(?=(?:[^a-zA-Z]*[a-zA-Z]))(?=(?:\D*\d))(?=(?:[^\W_]*[\W_])).{8,}$/
return !!passwordExpert.test(v) || 'It must be at least 8 characters and a combination of 3 or more letters, numbers, and special characters.'
},
lengthCheck: v => {
return v.length < 50 || 'The password you entered is too long.'
}
},
required: v => !!v || 'Field is required'
},
}),
methods: {
onSubmit () {
if (!this.form) return
this.loading = true
setTimeout(() => (this.loading = false), 2000)
},
emailDuplicateCheck (email) {
console.log(email)
// duplicateCheck: v => this.emailDuplicateCheck(v) || 'This is a duplicate email. Please use a different email.',
return true;
// true가 아닐 경우 'This is a duplicate email. Please use a different email.',
},
passwordMatchCheck (passwordVerify) {
return (this.password === passwordVerify) || 'It does not match your password.'
},
},
}
</script>