CRA 없이 React와 Typescript를 이용하여 slack 을 만들어보자!
const [password, , setPassword] = useInput('')
any
와 달리 제네릭을 사용하는 모두가 같은 type을 받을 수 있다는 장점.useInput.ts :
import { useState, useCallback, SetStateAction, Dispatch } from "react";
const useInput = <T = string>(initialData: T): [T, (e: any) => void, Dispatch<SetStateAction<T>>] => {
const [value, setValue] = useState(initialData);
const handler = useCallback((e)=>{
setValue(e.currentTarget.value);
},[]);
return [value, handler, setValue];
};
export default useInput;
<T = string>(initialData: T)
: T가 기본적으로 string이 들어오게 default 처리: [T, (e: any) => void, Dispatch<SetStateAction<T>>]
: 어떤 값을 return 할 지 type 체크Dispatch<SetStateAction<T>>
React에서 정의해줌type ReturnTypes<T = any> = [T, (e: any) => void, Dispatch<SetStateAction<T>>];
const useInput = <T = any>(initialData: T): ReturnTypes => {};
any
대신 ChangeEvent<HTMLInputElement>
, e.target.value
대신 e.target.value ad unknown as T
를 넣으면 해결signUp.tsx :
import React, {useCallback, useState} from "react";
import {Header, Form, Label, Input, Button, LinkContainer, Error} from "./styles";
import useInput from "@hooks/useInput";
const SignUp = () => {
const [email, handleChangeEmail, setEmail] = useInput('');
const [nickname, handleChangeNickname, setNickname] = useInput('');
const [password, ,setPassword] = useInput('');
const [passwordCheck, ,setPasswordCheck] = useInput('');
const handleChangePassword = useCallback((e)=>{
setPassword(e.target.value);
setMismatchError(e.target.value !== passwordCheck);
},[passwordCheck]);
const handleChangePasswordCheck = useCallback((e)=>{
setPasswordCheck(e.target.value);
setMismatchError(e.target.value !== password); // 기존 password 랑 같은지 비교하기
},[passwordCheck]);
return (
<div id="container">
<Header>Slack</Header>
<Form onSubmit={handleSubmit}>
<Label id="email-label">
<span>이메일 주소</span>
<div>
<Input
type="email"
name="email"
value={email}
onChange={handleChangeEmail} />
</div>
</Label>
<Label id="nickname-label">
<span>닉네임</span>
<Input
type="text"
name="nickname"
value={nickname}
onChange={handleChangeNickname} />
</Label>
<Label id="password">
<span>비밀번호</span>
<Input
type="password"
name="password"
value={password}
onChange={handleChangePassword} />
</Label>
<Label id="password-check-label">
<span>비밀번호 확인</span>
<div>
<Input
type="password"
id="password-check"
name="password-check"
value={passwordCheck}
onChange={handleChangePasswordCheck}
/>
</div>
</Label>
</div>
);
};
export default SignUp;
// 비동기 api 요청
axios.post('http://loalhost:3095/api/users', {
email, nickname, password
})
.then(()=>{}) // 성공
.catch(()=>{}) // 실패
.finally(()=>{}) // 성공 or 실패 둘 다 처리
OPTION
을 통해 한번 더 보냄.back-end app.js
에서 cors 허용 처리
app.use(
cors({
origin: true,
credentials: true,
})
);
webpack devServer
옵션에서 proxy 설정하기
devServer: {
historyApiFallback: true, // react router
port: 3090,
publicPath: '/dist/',
proxy: {
'/api/': { // front-end 에서 api로 보내는 요청은
target: 'http://localhost:3095', // 이런 주소로 보내겠다 변경한다는 뜻
changeOrigin: true,
},
},
},
axios.post("/api/users", {}); // url에 localhost 적어둔 거 제거하기
/api/users
가 백엔드 서버인 http://localhost:3095
로 우회되면서 cors 이슈 해결.const [signUpSuccess, setSignUpSuccess] = useState(false);
const [signUpError, setSignUpError] = useState('');
// 비동기 api 요청
const handleSubmit = useCallback((e) => {
e.preventDefault();
console.log(email, nickname, password, passwordCheck);
if (!mismatchError && nickname) {
setSignUpError(''); // 요청 보내기 전 미리 초기화
setSignUpSuccess(false);
// 비동기 api 요청
axios.post("/api/users", {
email, nickname, password
})
.then((response) => {
console.log(response);
setSignUpSuccess(true);
})
.catch((error) => {
console.log(error.response);
setSignUpError(error.response.data);
})
.finally(() => {
});
}
}, [email, nickname, password, passwordCheck]);
// return (
{signUpSuccess && <Success>회원가입되었습니다! 로그인해주세요.</Success>}
{signUpError !== '' && <Error>{signUpError}</Error>}}
// )
<a href="">
를 사용하면 새로고침 되므로 'react-router-dom'의 <Link>
를 사용.서버로부터 받아온 데이터를 가져와서 컴포넌트에게 전달하는 기능
전역으로 상태를 관리하여 사용할 수 있음. (ex. 로그인 후 내 정보 관리 )
react-query / GraphQL 사용 시 Apollo 가 같은 역할 함.
npm i swr
const {data, error, isValidating, mutate} = useSWR(key, fetcher, options);
login.tsx :
const {data, error} = useSWR('http://localhost:3095/api/users', fetcher);
// 첫번째 파라미터에는 요청 보낼 주소
// 두번째 파라미터에는 요청 받고 난 다음 처리 함수이름
utils/fetcher.ts :
import axios from 'axios';
const fetcher = (url: string) => {
axios.get(url).then((res)=> res.data).catch((err)=> err.data);
};
export default fetcher;
fetcher
는 따로 untils 파일로 분리하여 여러곳에서 사용 가능하도록 함
fetcher
함수의 매개변수인 url
은 useSWR
에 의해 자동으로 넘어감. fetcher 내에서 반환되는 data가 useSWR의 data 변수에 담기게 됨.
error
처리는 알아서 두번째 변수에 담김.
errorRetryInterval
정상 요청임에도 서버 에러가 날 경우, 스스로 서버에 interval 로 재요청 보낼 수 있음. loadingTimeout
을 이용해서 로딩 시 타임아웃이 걸렸을 때, 메시지 표시하게 처리할 수도 있음.현재 로그인 정보 전달은 보통 cookie를 이용함. but,,, 백엔드와 프론트 포트가 다르면 cookie 전달 불가능
axios.get(url, {
withCredentials: true // true로 설정하면 백엔드에서 생성한 쿠키 전달받을 수 있음
}).then((res)=> res.data).catch((err)=> err.data);
SWR은 몇 초간 주기적으로 서버에 요청을 보내게 됨. 잦은 요청을 방지하기 위해 revalidate()
함수를 실행.
const {data, error, revalidate} = useSWR('http://localhost:3095/api/users', fetcher);
axios.get(url, {
withCredentials: true // true로 설정하면 백엔드에서 생성한 쿠키 전달받을 수 있음
}).then((res)=> revalidate() ).catch((err)=> err.data);
const {data, error, revalidate} = useSWR('http://localhost:3095/api/users', fetcher, {dedupingInterval: 1000000}); // 1000초 뒤에 재호출
login.tsx :
const {data, error, revalidate} = useSWR('http://localhost:3095/api/users', fetcher);
// 비동기 api 요청
axios.post("http://localhost:3095/api/users/login", {
email, password
})
.then((response) => {
revalidate(); // 로그인 성공 시 get 재요청
})
.catch((error) => {
console.log(error.response);
setLoginError(error.response?.data?.statusCode === 401);
})
.finally(() => {
});
}, [email, nickname, password]);
// 로그인 성공 후 data 받아왔다면 리렌더링 되면서 channel 로 리다이렉트 처리
if(data){
return <Redirect to={"/workspace/channel"} />
}
Workspace.tsx :
import React, { FC, useCallback } from "react";
import useSWR from "swr/esm";
import fetcher from "@utils/fetcher";
import axios from "axios";
import { Redirect } from "react-router-dom";
const Workspace: FC = ({ children }) => {
const { data, error, revalidate } = useSWR("http://localhost:3095/api/users", fetcher);
const handleLogout = useCallback(() => {
axios.post("http://localhost:3095/api/users/logout", null, { withCredentials: true })
.then(() => {
revalidate(); // data null 처리
});
}, []);
// 로그인 데이터 없을 때 redirect 처리
// 이건 항상 hooks 보다 아래에 위치해야함...!!
if(!data){
return <Redirect to={"/login"}/>
}
return (
<div>
<button onClick={handleLogout}>로그아웃</button>
{children}
</div>
);
};
export default Workspace;
로그인 처리하기 전까지 data
는 false
처리 되므로 상태에 따른 분기 처리 가능.
data나 error 값이 변경되는 순간 컴포넌트 리렌더링 되므로 화면 전환을 시킬 수 있음.