처음 맡게 된 기능 구현 이슈카드는 <Signup 컴포넌트>였다.
해야할 것은
- 회원가입 컴포넌트 틀 구성
- 아이디, 이메일 유효성 검사
- 제출하기 버튼 -> redirect -> login 페이지
1. 회원가입 컴포넌트 틀 구성 - (Oct 17~18, 2020) 회고
회원가입 컴포넌트 틀을 짜는 것은 거의 HTML 같으니까 뭐... 라고 생각했지만
<div>와 <button> 그리고 <ul><li>만 쓰고 있는 나 자신을 발견했다.
시멘틱 마크업이 뭐길래? 아무튼 배우고 싶고 배워야 할 건 너어무 많다.
- <김버그의 HTML은 재밌다> 라는 강의가 있는데, 수강하고 싶다.
아무튼 예전에 HTML 태그 복습 해놓은게 있어서 그나마 다행이었다.
2. 아이디, 이메일 유효성 검사 - (Oct 18~19, 2020) 회고
아이디는 2개, 비밀번호는 1개의 유효성 검사를 실시한다.
1) 아이디: 아이디를 이메일로 받고 있기 때문에
1. key가 email인 경우, => 이메일 형식을 맞춰야 하고, 중복된 이메일이 존재하면 안된다. (이미 회원가입 되어있다는 의미이므로)
1-1. 이메일 형식이 맞지 않으면 => 올바른 이메일 형식이 아닙니다. 출력
1-2. 이메일 형식이 맞으면
1-2-1. 중복 검사 => 이미 있는 이메일이면 이미 존재하는 email입니다. 출력하고 올바른 이메일 입력할 때까지 break
1-2-2. => 없는 이메일이면 통과!
이렇게 한 글자 씩 입력해주면서 바로바로 중복검사가 이루어지기 때문에 모든 회원 정보를 다 입력하고 <제출하기>버튼을 눌렀을 때 post 요청에서는 DB에서 따로 중복 검사를 해주지 않아도 된다.
2) 비밀번호
2. key가 password인 경우, => 8자 이상이어야 하고, 숫자/소문자를 모두 포함해야 한다.
아래의 코드는 아직 API를 고려하기 전 단계로, 이메일 중복검사를 비롯한 여러 유효성 검사가 잘 작동하는지를 알아보기 위해 fakeUsersData를 사용했었다. 그러나 실제로는 check UsersId API를 통해서 중복검사를 진행하게 된다.
(코드는 더보기 클릭)
//Signup.js - state에 따라 or 라우팅에 따라) 변경되는 부분: x
import React from 'react';
import axios from 'axios';
import { withRouter } from 'react-router-dom';
import { fakeUsersData } from './__test__/fakeUsersData';
// console.log('fakeUsersData: ', fakeUsersData); //출력 잘 됨.
class SignUp extends React.Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
passwordCheck: '',
githubId: '',
isAvailedEmail: '',
isAvailedPassword: '',
isAvailedPasswordCheck: '',
};
}
handleSignUpValue = (key) => (e) => {
//여기서 유효성 검사를 한다.
// 1. key가 email인 경우, => 이메일 형식을 맞춰야 하고, 중복된 이메일이 존재하면 안된다. (이미 회원가입 되어있다는 의미이므로)
// 1-1. 이메일 형식이 맞지 않으면 => 올바른 이메일 형식이 아닙니다. 출력
// 1-2. 이메일 형식이 맞으면
// 1-2-1. 중복 검사 => 이미 있는 이메일이면 이미 존재하는 email입니다. 출력하고 올바른 이메일 입력할 때까지 break
// 1-2-2. => 없는 이메일이면 통과!
if (key === 'email') {
var emailreg = /^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$/i;
var email = e.target.value;
if (email.length > 0 && false === emailreg.test(email)) {
this.setState({ isAvailedEmail: '올바른 이메일 형식이 아닙니다.' });
} else {
//모든 이메일 형식을 잘 갖춰서 작성해 주었을 때, 중복 검사를 한다.
for (let userInfo of fakeUsersData) {//(*)이 부분이 현재는 fakeUsersData로 되어 있지만 나중에 check Userid API가 완성되면, 이 부분은 없어지고
console.log(userInfo);
if (userInfo.email === email) {
console.log(userInfo.email); //(*)여기서 글자 입력시마다 실시간으로 Check UsersId API를 통해 중복검사를 진행한는 코드를 추가할 계획이다.
this.setState({ isAvailedEmail: '이미 존재하는 email입니다.' });
break;
} else {
this.setState({ isAvailedEmail: '' });
this.setState({ [key]: e.target.value });
}
}
}
}
//2. key가 password인 경우, => 8자 이상이어야 하고, 숫자/소문자를 모두 포함해야 한다.
// reference: http://blog.naver.com/PostView.nhn?blogId=yjhyjh5369&logNo=221289465679&parentCategoryNo=&categoryNo=16&viewDate=&isShowPopularPosts=true&from=search
if (key === 'password') {
var reg = /^(?=.*?[a-z])(?=.*?[0-9]).{8,}$/;
var password = e.target.value;
if (password.length > 0 && false === reg.test(password)) {
this.setState({
isAvailedPassword: '비밀번호는 8자 이상이어야 하며, 숫자/소문자를 모두 포함해야 합니다.',
});
} else {
this.setState({ isAvailedPassword: '' });
this.setState({ [key]: e.target.value });
}
}
//3. key가 passwordCheck인 경우 => 앞선 this.state.password와 같아야한다.
if (key === 'passwordCheck') {
var passwordCheck = e.target.value;
if (passwordCheck.length > 0 && this.state.password !== passwordCheck) {
this.setState({ isAvailedPasswordCheck: '비밀번호가 일치하지 않습니다.' });
} else {
this.setState({ isAvailedPasswordCheck: '' });
this.setState({ [key]: e.target.value });
}
}
};
handleLoginButton = () => {
// 제출하기(회원가입) 버튼을 누르면 이 event가 발생
// 이 버튼은 서버에 회원가입을 요청 후 로그인 페이지로 리다이렉트 해줌
// 이미 회원가입이 되어 있는 경우, email 유효성 검사에서 걸러지므로 따로 확인 필요없음.
// axios 공식문서 https://xn--xy1bk56a.run/axios/guide/api.html#%EA%B5%AC%EC%84%B1-%EC%98%B5%EC%85%98 에서 //post 요청 전송 을 참고
axios({
method: 'post',
url: 'http://localhost:4000/user/signup',
data: {
email: this.state.email,
password: this.state.password,
githubId: this.state.githubId,
},
})
.then((res) => {
//200(OK), 201(Created)
console.log('SignUp res: ', res);
//history.pushState('/reviews');
})
.catch((err) => {
//500(err)
console.error(err);
});
};
render() {
const { history } = this.props;
return (
<div>
<ul>
<li>
<label htmlFor="email">
email
<input type="email" onChange={this.handleSignUpValue('email').bind(this)}></input>
<div>{this.state.isAvailedEmail}</div>
</label>
</li>
<li>
<label htmlFor="password">
pw
<input
type="password"
onChange={this.handleSignUpValue('password').bind(this)}
></input>
<div>{this.state.isAvailedPassword}</div>
</label>
</li>
<li>
<label
htmlFor="password check"
onChange={this.handleSignUpValue('passwordCheck').bind(this)}
>
pw 확인
<input type="password"></input>
<div>{this.state.isAvailedPasswordCheck}</div>
</label>
</li>
<li>
<label htmlFor="Github ID" onChange={this.handleSignUpValue('githubId').bind(this)}>
Github ID
<input></input>
</label>
</li>
</ul>
{/* <button onClick={this.handleLoginButton().bind(this)}>제출하기</button> */}
<button
onClick={(e) => {
// console.log(this.state);
e.preventDefault();
this.handleLoginButton.bind(this);
}}
>
제출하기
</button>
</div>
);
}
}
// export default withRouter(SignUp);
export default SignUp;
※ 이 과정에서 배운 것:
1) axios 의 사용
생각보다 공식문서가 잘 되어 있었다.
xn--xy1bk56a.run/axios/guide/api.html#%EA%B5%AC%EC%84%B1-%EC%98%B5%EC%85%98
2) 정규표현식
정규표현식이란
문자열을 검색하고 대체하는 데 사용 가능한 일종의 형식 언어(패턴)로,
이 표현식을 문자열에 적용하여 간단한 문자 검색부터 이메일, 패스워드 검사 등의 복잡한 문자 일치 기능 등을 빠르게 수행 할 수 있다.
정규표현식 학습의 필요성과 임하는 학습 목표
이메일의 형식이 맞는지, 비밀번호가 소문자/숫자 포함 8글자 이상인지를 구글에 검색해서 쉽게 구현하였지만 내 파일에 있는 몇 줄의 코드를 이해할 수 없다는 것에서 오는 찝찝함.
그리고 이메일, 비밀번호 형식등 일반적인 경우가 아니라 어떤 특정 문자패턴을 찾아내야하는 상황이 생긴다면? 이렇듯 어짜피 한 번은 공부해야 할거라고 생각했고, 이번이 그 기회라고 생각한다.
하지만 모든 경우의 수를 암기 하진 못 할 것이고, 나의 학습 목표는 표현식들이 모여있는 것을 뜯어보고 해독할 수 있는 수준, 정규식이 제공하는 기능을 알고 있는 것 이다.
<불규칙 속에서 규칙을 찾아내는 정규표현식>과 <정규표현식, 이렇게 시작하자!>를 많이 참고해서 공부중이다. 아직 정리 중이라 다 하면 첨부할 것이다!
우선, 내가 구글링으로 찾아서 사용한 정규표현식은 아래와 같다.
1. 이메일 형식
var emailreg = /^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$/i;
2. 소문자/숫자 포함 8글자 이상
var reg = /^(?=.*?[a-z])(?=.*?[0-9]).{8,}$/;
그 외에도 ,
- 하나 이상의 영문 대문자 (?=.*?[A-Z])
- 하나 이상의 영문 소문자 (?=.*?[a-z])
- 하나 이상의 숫자 (?=.*?[0-9])
- 하나 이상의 특수 문자 (?=.*?[#?!@$%^&*-])
- 최소 길이 8자 .{8,} (앵커 포함)
이런게 있었다.
그리고 정규표현식 테스트 사이트인 https://regexr.com/을 사용해서 연습하였다.
※. test() 메서드는 주어진 문자열이 정규 표현식을 만족하는지 판별하고, 그 여부를 true 또는 false로 반환합니다.
3. API를 통한 유효성 검사 & 제출하기 버튼 -> redirect -> login 페이지 - (Oct 20, 2020) 회고
server 쪽에서 회원가입에 필요한 API가 1차 완성되었다.
우선 SAFU 서비스의 회원가입 flow는 아래와 같다.
- 이메일을 입력한다.
- 이메일 한글자 입력시마다 check UserId API로 그걸 server에 보내주면
- server는 그걸 user DB에서 찾고
- 있으면 해당 data를 응답 -> 그러면 ㄴclient는 ‘존재하는 이메일입니다’라고 출력
- 없으면 nulll을 응답 -> 그러면 client는 아무 메세지도 출력하지 않고 통과시킴
- 그러면 그때서야 사용자는 password, GithubID를 입력하고, 다 입력하면 submit 버튼을 누른다. 그러면 그때 signup API가 작동하여서
- server에서는 users 테이블에 해당 데이터를 create.
즉, 우리가 필요한 API는 check UserId API, signup API이다.
signup API 의 경우 그냥 통으로 데이터를 보내주면 Check UserId API를 통해 이미 중복검사가 되어있으므로, DB에선 create만 해주면 되기때문에 client 쪽에서는 크게 어려운건 없었다. 다만 이메일 중복검사가 약간 헷갈릴 수 있는데 이럴때 gitbook(우리가 사용하는 API 문서)이 언제나 좋은 길잡이가 되었다. server와 client의 통신인 만큼 API문서가 가장 좋은 커뮤니케이션 기준, 코드 구현 기준이다.
다음 project에는 response의 데이터 형식을 완전 json으로 좀 더 구체적으로 적고, DB column명과도 일치시키면 더 좋을 것 같다.
1) check UserId API
아무튼 Check UserId API는 아래와 같이 기획하였기 때문에
fakeUsersData로 email 중복검사에 했던 코드 부분은 이렇게 바뀔 것이다.
client code)
handleSignUpValue = (key) => (e) => {
if (key === 'useremail') {
var emailreg = /^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$/i;
var useremail = e.target.value;
this.setState({ [key]: useremail });
if (useremail.length > 0 && false === emailreg.test(useremail)) {
this.setState({ isAvailedEmail: '올바른 이메일 형식이 아닙니다.' });
} else {
axios({
method: 'post',
url: 'http://localhost:4000/users/signup/checkId',
data: {
useremail: e.target.value,// 이렇게 하면 씽크가 맞다. jhjyj5414@naver.com으로 검사를 진행하게 된다.
// useremail: this.state.useremail, //이렇게 하면 한 박자 느리다. jhjyj5414@naver.co으로 검사를 진행하게 된다. 왜일까?
},
})
.then((res) => {
//status code: 200
if (res.data !== null) { //DB의 데이터가 담겨서 오는 것은 res.data이다.
this.setState({ isAvailedEmail: '이미 존재하는 email입니다.' });
} else {
this.setState({ isAvailedEmail: '' });
this.setState({ [key]: useremail });
}
})
.catch((err) => {
//status code: 500
console.error(err);
});
}
}
추가로 server code까지 보면..ㅎㅎ)
const { users } = require('../../models');
module.exports = {
post: (req, res) => {
const { useremail } = req.body;
users
.findOne({
where: {
email: useremail,
},
})
.then((data) => {
console.log(data);
if (data !== null) {
res.status(201).json(data);
} else {
res.status(201).json(data);
}
})
.catch((err) => {
res.status(500).send('err');
});
},
};
2) signup API
signup submit 버튼 눌렀을 때 DB의 users 테이블에 잘 들어가는 것 까지 확인하였다. 원래 서비스 기능 flow상,
제출하기 버튼을 누르면 login 페이지로 redirect 된다. 하지만 아직 login이 구현되기 전이라 이 부분은 주석처리 해 놓았고 API 통신이 잘 되는지만 확인하려고 console로 '회원가입 완료'를 찍는 것으로 마무리하였다.
signup submit 버튼 눌렀을 때 client)
handleSignUpButton = () => {
axios({
method: 'post',
url: 'http://localhost:4000/users/signup',
data: {
useremail: this.state.useremail,
password: this.state.password,
githubId: this.state.githubId,
},
})
.then((res) => {
//200(OK), 201(Created)
// this.props.history.push('/users/login');
console.log('회원가입 완료');
})
.catch((err) => {
//500(err)
console.error(err);
});
};
signup submit 버튼 눌렀을 때 server)
const { users } = require('../../models');
module.exports = {
post: (req, res) => {
const { useremail, password, githubId } = req.body;
users
.create({
email: useremail,
password: password,
githubId: githubId,
active: true,
})
.then((data) => {
res.status(201).json(data);
})
.catch((err) => {
res.status(500).send('err');
});
},
};
여기까지 완성된 code
//Signup.js - state에 따라 or 라우팅에 따라) 변경되는 부분: x
import React from 'react';
import axios from 'axios';
import { withRouter } from 'react-router-dom';
class SignUp extends React.Component {
constructor(props) {
super(props);
this.state = {
useremail: '',
password: '',
passwordCheck: '',
githubId: '',
isAvailedEmail: '',
isAvailedPassword: '',
isAvailedPasswordCheck: '',
};
}
handleSignUpValue = (key) => (e) => {
//여기서 유효성 검사를 한다.
// 1. key가 email인 경우, => 이메일 형식을 맞춰야 하고, 중복된 이메일이 존재하면 안된다. (이미 회원가입 되어있다는 의미이므로)
// 1-1. 이메일 형식이 맞지 않으면 => 올바른 이메일 형식이 아닙니다. 출력
// 1-2. 이메일 형식이 맞으면
// 1-2-1. 중복 검사 => 이미 있는 이메일이면 이미 존재하는 email입니다. 출력하고 올바른 이메일 입력할 때까지 break
// 1-2-2. => 없는 이메일이면 통과!
if (key === 'useremail') {
var emailreg = /^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$/i;
var useremail = e.target.value;
this.setState({ [key]: useremail });
if (useremail.length > 0 && false === emailreg.test(useremail)) { //test() 메서드는 주어진 문자열이 정규 표현식을 만족하는지 판별하고, 그 여부를 true 또는 false로 반환한다.
this.setState({ isAvailedEmail: '올바른 이메일 형식이 아닙니다.' });
} else {//모든 이메일 형식을 잘 갖춰서 작성해 주었을 때, check UserId API를 통해 중복 검사를 한다.
axios({
method: 'post',
url: 'http://localhost:4000/users/signup/checkId',
data: {
useremail: e.target.value,
},
})
.then((res) => {
if (res.data !== null) {
this.setState({ isAvailedEmail: '이미 존재하는 email입니다.' });
} else {
this.setState({ isAvailedEmail: '' });
this.setState({ [key]: useremail });
}
})
.catch((err) => {
console.error(err);
});
}
}
//2. key가 password인 경우, => 8자 이상이어야 하고, 숫자/소문자를 모두 포함해야 한다.
if (key === 'password') {
var reg = /^(?=.*?[a-z])(?=.*?[0-9]).{8,}$/;
var password = e.target.value;
if (password.length > 0 && false === reg.test(password)) {
this.setState({
isAvailedPassword: '비밀번호는 8자 이상이어야 하며, 숫자/소문자를 모두 포함해야 합니다.',
});
} else {
this.setState({ isAvailedPassword: '' });
this.setState({ [key]: e.target.value });
}
}
//3. key가 passwordCheck인 경우 => 앞선 this.state.password와 같아야한다.
if (key === 'passwordCheck') {
var passwordCheck = e.target.value;
if (passwordCheck.length > 0 && this.state.password !== passwordCheck) {
this.setState({ isAvailedPasswordCheck: '비밀번호가 일치하지 않습니다.' });
} else {
this.setState({ isAvailedPasswordCheck: '' });
this.setState({ [key]: e.target.value });
}
}
if (key === 'githubId') {
this.setState({ [key]: e.target.value });
}
};
handleSignUpButton = () => {
// 제출하기(회원가입) 버튼을 누르면 이 event가 발생
// 이 버튼은 서버에 회원가입을 요청 후 로그인 페이지로 리다이렉트 해줌
// 이미 회원가입이 되어 있는 경우, email 유효성 검사에서 걸러지므로 따로 확인 필요없음.
axios({
method: 'post',
url: 'http://localhost:4000/users/signup',
data: {
useremail: this.state.useremail,
password: this.state.password,
githubId: this.state.githubId,
},
})
.then((res) => {
//200(OK), 201(Created)
// this.props.history.push('/users/login');
console.log('회원가입 완료');
})
.catch((err) => {
//500(err)
console.error(err);
});
};
render() {
const { history } = this.props;
return (
<div>
<ul>
<li>
<label htmlFor="useremail">
<div>email</div>
<input type="useremail" onChange={this.handleSignUpValue('useremail')}></input>
<div>{this.state.isAvailedEmail}</div>
</label>
</li>
<li>
<label htmlFor="password">
<div>password</div>
<input type="password" onChange={this.handleSignUpValue('password')}></input>
<div>{this.state.isAvailedPassword}</div>
</label>
</li>
<li>
<label htmlFor="password check" onChange={this.handleSignUpValue('passwordCheck')}>
<div>password 확인</div>
<input type="password"></input>
<div>{this.state.isAvailedPasswordCheck}</div>
</label>
</li>
<li>
<label htmlFor="Github ID" onChange={this.handleSignUpValue('githubId')}>
<div>Github ID (for identification)</div>
<input></input>
</label>
</li>
</ul>
<div>
<button
onClick={(e) => {
e.preventDefault();
{
this.handleSignUpButton();
}
}}
>
Submit
</button>
</div>
</div>
);
}
}
// export default withRouter(SignUp);
export default SignUp;
※ 이 과정에서 배운 것 2:
1) 이전에 server에서 응답으로 주는 데이터를 client에서는 어떻게 다루는지 배운 것을 복습했다.
동영상 정보를 요청하고 응답받는 Youtube API 이용
const searchYouTube = ({ key, query, max = 5 }, callback) => {
fetch(
`https://www.googleapis.com/youtube/v3/search?part=snippet&key=${key}&q=${query}&maxResult=${max}&type=video&videoEmbeddable=true`,
{
method: 'GET',
},
)
.then((resp) => resp.json())
.then(({ items }) => {
callback(items)
})
}
onSearchSumit(term) {
var options = {
key: YOUTUBE_API_KEY,
query: term,
}
searchYouTube(options, (videos) =>
this.setState({
videos: videos,
selectedVideo: videos[0],
}),
)
}
2) React에서 이벤트 다루기의 bind에 대해서 다시 공부
3) 응답이 오는 형태에 대해서 확실히 확인!
그러니까 response.data에 내가 원하는 데이터가 담겨서 응답으로 온다!
'Project > [SAFU] 1st Project' 카테고리의 다른 글
6. [Client & Server] Findid, Findpw 구현 (Oct 24, 2020 ~ Oct 25, 2020 회고) (0) | 2020.11.05 |
---|---|
5. [Client & Server] Login, Logout 구현 (Oct 21, 2020 ~ Oct 25, 2020 회고) (0) | 2020.11.05 |
3. [Server] Sequelize DB 세팅(Oct 17~18, 2020 회고) (0) | 2020.11.05 |
2. [Basic] 프로젝트 준비 - 프로젝트 협업을 위한 Gitflow (0) | 2020.10.18 |
1. [Basic] 프로젝트 Intro, 첫 번째 미팅 (0) | 2020.10.18 |