import React, { useState } from "react";
type LoginSuccessMessage = "SUCCESS";
type LoginFailMessage = "FAIL";
interface LoginResponse {
message: LoginSuccessMessage | LoginFailMessage;
token: string;
}
interface UserInfo {
name: string;
}
interface User {
username: string;
password: string;
userInfo: UserInfo;
}
const users: User[] = [
{
username: "blue",
password: "1234",
userInfo: { name: "blueStragglr" },
},
{
username: "white",
password: "1234",
userInfo: { name: "whiteDwarf" },
},
{
username: "red",
password: "1234",
userInfo: { name: "redGiant" },
},
];
const _secret: string = "1234qwer!@#$";
const login = async (username: string, password: string): Promise<LoginResponse | null> => {
const user: User | undefined = users.find((user: User) => {
return user.username === username && user.password === password;
});
return user
? { message: "SUCCESS", token: JSON.stringify({ user: user.userInfo, secret: _secret }) }
: null;
};
const getUserInfo = async (token: string): Promise<UserInfo | null> => {
const parsedToken = JSON.parse(token);
if (!parsedToken?.secret || parsedToken.secret !== _secret) return null;
const loggedUser: User | undefined = users.find((user: User) => {
if (user.userInfo.name === parsedToken.user.name) return user;
});
return loggedUser ? loggedUser.userInfo : null;
};
const LoginWithMockAPI = () => {
const [userInfo, setUserInfo] = useState<UserInfo>({ name: "" });
const loginSubmitHandler = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const loginRes = await login(formData.get("username") as string, formData.get("password") as string);
if (!loginRes) return;
const userInfo = await getUserInfo(loginRes.token);
if (!userInfo) return;
setUserInfo(userInfo);
};
return (
<div>
<h1>Login with Mock API</h1>
<form onSubmit={loginSubmitHandler}>
<label>
Username:
<input type="text" name="username" />
</label>
<label>
Password:
<input type="password" name="password" />
</label>
<button type="submit" value="Submit">
submit
</button>
</form>
<div>
<h2>User info</h2>
{JSON.stringify(userInfo)}
</div>
</div>
);
};
export default LoginWithMockAPI;
코드에 모르는 부분이 많았다.
그래서 세세하게 분석하면서 코드를 읽어보고자한다. 뭐 이렇게 하다보면 확실하게 기억하겠지..
1. 타입 정의 및 인터페이스
type LoginSuccessMessage = "SUCCESS";
type LoginFailMessage = "FAIL";
interface LoginResponse {
message: LoginSuccessMessage | LoginFailMessage;
token: string;
}
interface UserInfo {
name: string;
}
interface User {
username: string;
password: string;
userInfo: UserInfo;
}
문자열 리터럴 타입으로, 로그인 성공 또는 실패 메시지를 정의
type LoginSuccessMessage = "SUCCESS";
type LoginFailMessage = "FAIL";
로그인 결과를 나타내는 인터페이스 : message와 token 두 속성을 가지고 있다.
interface LoginResponse {
message: LoginSuccessMessage | LoginFailMessage;
token: string;
}
사용자 정보를 나타내는 인터페이스
- interface UserInfo {
name: string;
} - interface User {
username: string;
password: string;
userInfo: UserInfo;
}
2. 가짜데이터임
const users: User[] = [
{
username: "blue",
password: "1234",
userInfo: { name: "blueStragglr" },
},
{
username: "white",
password: "1234",
userInfo: { name: "whiteDwarf" },
},
{
username: "red",
password: "1234",
userInfo: { name: "redGiant" },
},
];
3. 비밀 정보
const _secret: string = "1234qwer!@#$";
4. 로그인 함수 (login)
const login = async (username: string, password: string): Promise<LoginResponse | null> => {
// 1. 사용자 배열(users)에서 주어진 사용자 이름(username)과 비밀번호(password)을 가진 사용자를 찾습니다.
const user: User | undefined = users.find((user: User) => {
return user.username === username && user.password === password;
});
// 2. 사용자가 존재하면 로그인이 성공한 것으로 간주하고 로그인 응답 객체를 생성합니다.
// 이 응답 객체에는 "SUCCESS" 메시지와 사용자 정보 및 비밀 정보가 포함된 토큰이 들어갑니다.
return user
? { message: "SUCCESS", token: JSON.stringify({ user: user.userInfo, secret: _secret }) }
: null;
};
1) const login = async (username: string, password: string): Promise<LoginResponse | null> => {
- login 함수는 비동기 함수 (async)로 선언
로그인은 보통 서버와 통신해야 하고, 이는 시간이 걸릴 수 있는 비동기 작업입니다.
사용자가 로그인 버튼을 클릭한 후에도 다른 작업을 수행하거나 화면을 업데이트할 수 있어야 합니다 - 함수는 username과 passsword 두 개의 문자열 매개변수
2) token: JSON.stringify({ user: user.userInfo, secret: _secret })
- 정보를 JSON 문자열로 변환
주로 데이터를 문자열로 변환하여 전송하거나 저장하기 위함
웹 애플리케이션에서 서버로 데이터를 보내거나, 브라우저의 로컬 스토리지에 데이터를 저장할 때,
데이터를 문자열로 직렬화하여 전송 또는 저장하는 것이 일반적
5. 사용자 정보 가져오기 함수 (getUserInfo)
const getUserInfo = async (token: string): Promise<UserInfo | null> => {
// 1. 주어진 토큰을 JSON 파싱하여 객체로 변환합니다.
const parsedToken = JSON.parse(token);
// 2. 파싱한 토큰의 유효성을 검사합니다.
if (!parsedToken?.secret || parsedToken.secret !== _secret) return null;
// 3. 유효한 토큰인 경우, 토큰에 저장된 사용자 정보와 일치하는 사용자를 찾습니다.
const loggedUser: User | undefined = users.find((user: User) => {
if (user.userInfo.name === parsedToken.user.name) return user;
});
// 4. 사용자를 찾은 경우 해당 사용자의 정보를 반환하고, 찾지 못한 경우 null을 반환합니다.
return loggedUser ? loggedUser.userInfo : null;
};
1) if (!parsedToken?.secret || parsedToken.secret !== _secret) return null;
- 로그인 토큰(parsedToken)의 유효성을 검사하는데 사용
- parsedToken?.secret 부분은 옵셔널 체이닝을 사용하여 토큰 객체(parsedToken)의 secret 속성에 안전하게 접근합니다.
- 토큰 객체(parsedToken)가 존재하면서 secret 속성이 존재하고, 그 값이 falsy하지 않은 경우에는 이 부분이 거짓(false)이 됩니다. 이 경우에는 토큰의 secret 속성이 유효하다는 의미입니다.
6. 로그인 컴포넌트 (LoginWithMockAPI)
const LoginWithMockAPI = () => {
const [userInfo, setUserInfo] = useState<UserInfo>({ name: "" });
const loginSubmitHandler = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const loginRes = await login(formData.get("username") as string, formData.get("password") as string);
if (!loginRes) return;
const userInfo = await getUserInfo(loginRes.token);
if (!userInfo) return;
setUserInfo(userInfo);
}
1) const [userInfo, setUserInfo] = useState<UserInfo>({ name: "" });
interface UserInfo {
name: string;
}
2) const formData = new FormData(event.currentTarget);
- FormData 객체를 생성하여 폼(form) 데이터를 수집합니다. event.currentTarget는 현재 폼 엘리먼트를 나타냅니다.
- 이를 통해 폼의 입력 필드에 입력된 데이터를 수집할 수 있습니다.
여태껏 폼의 입력 필드에 입력된 데이터는 state에 저장해서 value로 보여줬었는데..!!
const idHandler = (e: { target: { value: React.SetStateAction<string> } }) => { setId(e.target.value);};
3) login 함수를 호출
const loginRes = await login(formData.get("username") as string, formData.get("password") as string);
if (!loginRes) return;
login 함수를 호출하여 로그인을 시도합니다.
formData.get("username")과 formData.get("password")를 사용하여 사용자가 입력한 유저네임과 패스워드를 전달합니다.
await 키워드를 사용하여 login 함수가 비동기로 실행되는 것을 기다립니다.
TypeScript의 타입 단언(as)을 사용하여 반환되는 값이 문자열(string)임을 명시적으로 지정
만약 login 함수가 null을 반환하면, 로그인이 실패한 것으로 가정하고 함수를 종료합니다.
4) getUserInfo 함수 호출
const userInfo = await getUserInfo(loginRes.token);
if (!userInfo) return;
로그인이 성공한 경우,
getUserInfo 함수를 사용하여 로그인된 사용자의 정보를 가져옵니다.
이 때, loginRes.token을 사용하여 토큰을 전달합니다.
마찬가지로 await 키워드를 사용하여 getUserInfo 함수가 비동기로 실행되는 것을 기다립니다.
만약 getUserInfo 함수가 null을 반환하면, 사용자 정보를 가져오지 못한 것으로 가정하고 함수를 종료합니다.
Q. 단언(as) 타입은 어떻게 사용하나요?
as : 특정 값의 타입을 명시적으로 지정
개발자가 컴파일러보다 더 정확한 타입 정보를 가지고 있을 때 유용
const someValue: any = "Hello, TypeScript!";
const strLength: number = (someValue as string).length;
// 또는
const strLength: number = (<string>someValue).length;
as 키워드를 사용한 단언
- as 키워드 다음에 원하는 타입을 명시합니다.
- 이 형태의 단언은 JSX와 함께 사용할 때 권장되는 형태입니다.
각괄호(< >)를 사용한 단언
-
- <와 > 사이에 원하는 타입을 넣습니다.
- 이 형태의 단언은 JSX와 함께 사용할 때 문법 충돌이 발생할 수 있으므로 대부분의 경우 as 키워드를 사용하는 것이 권장됩니다.
*단언을 사용할 때는 가능한한 확실한 경우에만 사용하십시오.*