[CocaVoca] 개발일지 #1 - 로그인 폼 제작
- -
본격! 리액트 + 타입스크립트 + 노드로 만드는 회원가입 폼이다.
폼은 개념만 배우고 제대로 만들어 보는 건 처음이라서 여러 가지 문서를 참고했다.
1. form - GET / POST
회원가입 form method로 GET, POST 중 어느 것을 사용하는지 알아보기 위해 mdn 문서를 찾아봤다.
GET
GET 메서드는 브라우저가 서버에 주어진 리소스를 보내달라고 요청하는 데 사용한다. 즉 데이터를 조회할 때 많이 쓴다.
브라우저는 빈 본문을 전송하고, 본문이 비어 있으므로 GET 메서드를 사용하여 양식을 전송하면 서버로 전송된 데이터가 URL에 추가된다.
export default function Login(): JSX.Element {
return (
<div>
<form action="/url" method="get">
<div className="input-box">
<label htmlFor="id">아이디</label>
<input
type="text"
name="id"
id="id"
placeholder="6-12자 이내, 영문, 숫자 조합"
autoFocus
/>
<button type="submit">중복확인</button>
</div>
<div className="input-box">
<label htmlFor="password">비밀번호</label>
<input
type="text"
name="password"
id="password"
placeholder="8자 이상, 영문, 숫자, 특수문자 조합"
/>
<br />
</div>
<div className="input-box">
<label htmlFor="nickname">닉네임</label>
<input
type="text"
name="nickname"
id="nickname"
placeholder="공백없이 영문, 한글, 숫자(2-12자 이내)"
/>
<button type="submit">중복확인</button>
</div>
<button type="submit">가입하기</button>
</form>
</div>
);
}
// 가입하기 버튼 클릭 후 URL
http://localhost:3000/url?id=test_id&password=test_pw&nickname=test_nickname
// 개발자 도구 - Network
Request URL:
http://localhost:3000/url?id=test_id&password=test_pw&nickname=test_nickname
Request Method:
GET
Status Code:
304 Not Modified
설명대로 URL 안에 인풋에 입력한 데이터가 그대로 노출된다. 또한 개발자 도구의 Network 카테고리란에 있는 Headers에
GET 메소드로 전송한 요청 정보와 상태 코드가 나와있다. 사용자 정보가 그대로 노출되니 보안상 위험할 것 같다.
POST
HTTP POST 방식은 서버에 데이터를 전송하고 리소스를 생성할 때 주로 사용한다.
요청 본문의 유형은 Content-Type 헤더에 의해 지시된다.
PUT과 POST의 차이점은 PUT이 idempotent(멱등성: 연산을 여러 번 적용하더라도 결과가 달라지지 않는 성질)하다는 점이다.
위와 동일한 코드에 method만 POST로 수정해보았다.
Request URL:
http://localhost:3000/url
Request Method:
POST
Status Code:
404 Not Found
실제로 Requst URL에 담긴 페이지가 없어서 화면에는 Cannot POST / url이 떴고, Headers의 정보는 위처럼 떴다.
1. 비밀번호(또는 기타 민감한 데이터)를 전송해야 하는 경우 GET 방식 사용시 URL 표시줄에 표시되어 위험하다.
2. 대량의 데이터를 전송해야 하는 경우 일부 브라우저는 URL 크기를 제한하므로 POST 방법을 사용하는 것이 좋다.
또한 많은 서버가 허용하는 URL 길이를 제한합니다.
위와 같은 이유로 POST를 적용해서 가입 폼을 만들기로 했다.
2. React form
react-hook-form이라는 간편한 라이브러리가 있다고 하는데, 우선은 순수 리액트로 개발하면서 기능을 익히려고 한다.
import { useState } from "react";
export default function Login(): JSX.Element {
const [userID, setUserID] = useState("");
const [userPassword, setUserPW] = useState("");
const [userNickname, setUserNickname] = useState("");
const onUserIDHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
setUserID(e.target.value);
};
const onUserPWHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
setUserPW(e.target.value);
};
const onUserNicknameHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
setUserNickname(e.target.value);
};
console.log(userID, userPassword, userNickname);
return (
<div>
<form action="/" method="post">
<div className="input-box">
<label htmlFor="id">아이디</label>
<input
type="text"
name="user_id"
id="id"
placeholder="6-12자 이내, 영문, 숫자 조합"
autoFocus
required
pattern="^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{6,12}$"
minLength={6}
maxLength={12}
value={userID}
onChange={onUserIDHandler}
/>
<button type="submit">중복확인</button>
</div>
<div className="input-box">
<label htmlFor="password">비밀번호</label>
<input
type="text"
name="user_pw"
id="password"
placeholder="8자 이상, 영문, 숫자, 특수문자 조합"
required
minLength={8}
maxLength={16}
pattern="^(?=.*[A-Za-z])(?=.*\d)(?=.*[!@#$%^*+=-]).{8,}$"
value={userPassword}
onChange={onUserPWHandler}
/>
</div>
<div className="input-box">
<label htmlFor="nickname">닉네임</label>
<input
type="text"
name="user_nickname"
id="nickname"
placeholder="공백없이 영문, 한글, 숫자(2-12자 이내)"
required
minLength={2}
maxLength={12}
pattern="^[a-zA-Z가-힣0-9]{2,12}$"
value={userNickname}
onChange={onUserNicknameHandler}
/>
<button type="submit">중복확인</button>
</div>
<button type="submit">가입하기</button>
</form>
</div>
);
}
인풋에 들어오는 값들(id, password, nickname)은 입력하면 상태가 계속 바뀌므로 state에 저장해준다.
우선은 하드코딩해서 인풋에 들어온 값들이 출력되는지 확인해보았다.
그 다음은 비슷한 구조의 state와 eventhandler 함수를 객체로 구조화해준다.
import { useState } from "react";
export default function Login(): JSX.Element {
const [userInfo, setUserInfo] = useState({
id: "",
password: "",
nickname: ""
});
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setUserInfo({
...userInfo, // 기존 객체를 복사해 mutation 막기
[e.target.name] : e.target.value
})
};
console.log({...userInfo});
return (
<div>
<form action="/" method="post">
<div className="input-box">
<label htmlFor="id">아이디</label>
<input
type="text"
name="id"
id="id"
placeholder="6-12자 이내, 영문, 숫자 조합"
autoFocus
required
pattern="^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{6,12}$"
minLength={6}
maxLength={12}
value={userInfo.id}
onChange={handleChange}
/>
<button type="submit">중복확인</button>
</div>
<div className="input-box">
<label htmlFor="password">비밀번호</label>
<input
type="text"
name="password"
id="password"
placeholder="8자 이상, 영문, 숫자, 특수문자 조합"
required
minLength={8}
maxLength={16}
pattern="^(?=.*[A-Za-z])(?=.*\d)(?=.*[!@#$%^*+=-]).{8,}$"
value={userInfo.password}
onChange={handleChange}
/>
</div>
<div className="input-box">
<label htmlFor="nickname">닉네임</label>
<input
type="text"
name="nickname"
id="nickname"
placeholder="공백없이 영문, 한글, 숫자(2-12자 이내)"
required
minLength={2}
maxLength={12}
pattern="^[a-zA-Z가-힣0-9]{2,12}$"
value={userInfo.nickname}
onChange={handleChange}
/>
<button type="submit">중복확인</button>
</div>
<button type="submit">가입하기</button>
</form>
</div>
);
}
handleChange 함수에서 userInfo 객체를 복사해서 새로 입력되는 값을 저장한다.
객체를 복사하는 이유는 mutation(변이)를 방지하기 위해서인데, 리액트 공식문서에 따르면
obj.key = value 처럼 직접 객체 값을 바꿀 수 있지만, 객체를 직접 변경하면 이전 상태와 비교해서
어떤 부분이 변경되었는지 확인하기 어렵다.
객체를 복사해서 교체하면 불변성을 유지하여 리렌더링 성능을 최적화할 수 있고,
이전 상태와 비교가 용이해지며 필요한 경우에만 리렌더링이 발생해 성능을 향상시킬 수 있다.
따라서 number, boolean, string처럼 변이 불가능하다 생각하고 새로운 객체를 생성해서 교체해야 한다.
특히 배열 사용시에는 concat, [...spread syntax], filter, slice, map 등을 사용해 불변성을 유지한다.
3. Valiadation Check
import { useState } from "react";
import styled from "styled-components";
export default function Login(): JSX.Element {
const InputMessage = styled.p`
font-size: 0.75rem;
color: "black";
`;
// 유저 인풋 정보
const [userInfo, setUserInfo] = useState({
id: "",
password: "",
nickname: "",
});
// 인풋 안내 메시지 문구
const [inputMessage, setInputMessage] = useState({
id: {
default: "6-12자 이내, 영문, 숫자 조합으로 입력해주세요.",
duplication: "등록된 아이디입니다.",
},
password: {
default: "8-12자 이내, 영문, 숫자 조합으로 입력해주세요.",
},
nickname: {
default: "2-12자 이내 영문/한글/숫자를 입력해주세요.",
duplication: "등록된 닉네임입니다.",
},
});
// 유저 인풋 감지
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setUserInfo({
...userInfo,
[e.target.name]: e.target.value,
});
};
return (
<div>
<form action="/" method="post">
<div className="input-box">
<label htmlFor="id">아이디</label>
<input
type="text"
name="id"
id="id"
placeholder="6-12자 이내, 영문, 숫자 조합"
autoFocus
required
pattern="^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{6,12}$"
minLength={6}
maxLength={12}
value={userInfo.id}
onChange={handleChange}
/>
<InputMessage>{inputMessage.id.default}</InputMessage>
</div>
<div className="input-box">
<label htmlFor="password">비밀번호</label>
<input
type="text"
name="password"
id="password"
placeholder="8-12자 이내, 영문, 숫자, 특수문자 조합"
required
minLength={8}
maxLength={16}
pattern="^(?=.*[A-Za-z])(?=.*\d)(?=.*[!@#$%^*+=-]).{8,12}$"
value={userInfo.password}
onChange={handleChange}
/>
<InputMessage>{inputMessage.password.default}</InputMessage>
</div>
<div className="input-box">
<label htmlFor="nickname">닉네임</label>
<input
type="text"
name="nickname"
id="nickname"
placeholder="2-12자 이내 영문/한글/숫자"
required
minLength={2}
maxLength={12}
pattern="^[a-zA-Z가-힣0-9]{2,12}$"
value={userInfo.nickname}
onChange={handleChange}
/>
<InputMessage>{inputMessage.nickname.default}</InputMessage>
</div>
<button>가입하기</button>
</form>
</div>
);
}
인풋값이 조건식에 만족하지 않으면 글자가 빨간색으로 변하게 하고,
아이디와 닉네임의 경우 중복 값이 있으면 문구를 "등록된 아이디입니다."와 같이 바꾸려고 했다.
근데 중복 값은 데이터가 있어야 가능하고 스타일은 기능 먼저 만들고 나중에 개발하는 게 나을 것 같다.
서버를 미리 만들었어야 했나? 프론트 -> 서버 순서로 개발하려고 했는데,
유저 정보를 담은 데이터와 로그인 완료 페이지가 없어서 기능을 만들기가 애매하다.
아니면 프론트, 백 동시에 개발하면서 진행하는 게 맞나.
어떤 순서로 개발을 해야할지, 페이지 구조는 어떻게 해야 할지 조금 더 고민해봐야겠다.
그나저나 폼 속성이 많아서 코드가 너무 길어 ... ^^
아니 그 전에 리액트 자체가 라이브러리를 너무 많이 쓰는 것 같음
정말.. 효율적으로 만들 수 있는 거 맞겠지 내가 아직 부족한 거겠지? 하하
참고 자료
Sending form data - Learn web development | MDN
As we'd alluded to above, sending form data is easy, but securing an application can be tricky. Just remember that a front-end developer is not the one who should define the security model of the data. It's possible to perform client-side form validation,
developer.mozilla.org
Client-side form validation - Learn web development | MDN
Client-side form validation sometimes requires JavaScript if you want to customize styling and error messages, but it always requires you to think carefully about the user. Always remember to help your users correct the data they provide. To that end, be s
developer.mozilla.org
Updating Objects in State – React
The library for web and native user interfaces
react.dev
'DevLog > Project' 카테고리의 다른 글
[Portfolio] Next.js로 하루 만에 포트폴리오 만들고 배포하기 (0) | 2023.11.27 |
---|---|
[CocaVoca] 개발일지 #4 - naver 아이디로 nodemailer 설정 + 이메일 회원가입 기능 세팅 (0) | 2023.10.17 |
[CocaVoca] 개발일지 #3 - DB 설계 및 node mysql2 라이브러리 + AWS RDS 연결 (0) | 2023.10.16 |
[CocaVoca] 개발일지 #2 - 카카오 REST API 로그인 기능 - 인가 코드 받기 (0) | 2023.10.09 |
소중한 공감 감사합니다