react-router,
async,
useState
프로젝트 파일 구조는 다음과 같다.
App.js
: 리액트 라우터를 사용하기 위해 기본적인 준비auth.js
: 서버를 대체할 인증 모듈AuthRoute.js
: 인증이 필요한 컴포넌트를 위해 구분LoginForm.js
: 로그인 폼(email,password) LogoutButton.js
: 로그아웃 버튼은 기능이 많아 따로 컴포넌트로 생성해 주었다.Profile.js
: 로그인(인증) 후 들어갈 수 있는 프로필 컴포넌트RegisterForm.js
: 회원가입 폼(name,email,password)About.js
: 인증 없이도 들어갈 수 있는 평범한 페이지Home.js
: 메인 디렉토리설명을 위해 코드를 나눠서 설명하겠다.
import React, { useState } from 'react'
import { Link, Route, Switch, BrowserRouter as Router } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Profile from './components/Profile';
import { signIn } from './auth/auth';
import AuthRoute from './auth/AuthRoute';
import LogoutButton from './components/LogoutButton';
import LoginForm from './components/LoginForm';
import RegisterForm from './components/RegisterForm';
react-router-dom
라이브러리에서 Link, Route, Switch, BrowserRouter as Router를 가져온다.
Link
: 클릭 시 이동하는 url을 지정
Switch
: Route
중에 일치하는 첫 번째 요소 불러옴
BrowserRouter
: url, ui를 동기화시킴
여기서는 BrowserRouter as Router
를 통해 BrowserRouter
대신에 Router
를 사용했다.(이름 변경)
const App = () => {
const [user, setUser] = useState(null);
// authenticated: 로그인 상태 확인
const authenticated = user != null;
// 로그인, 로그아웃
const login = ({ email, password }) => setUser(signIn({ email, password }));
const logout = () => setUser(null);
// 회원가입
const [signUp, setSIgnUp] = useState(null);
const signUpCompleted = ({ sign }) => setSIgnUp({ sign });
return (
<Router>
<header>
<Link to="/">
<button>Home</button>
</Link>
<Link to="/about">
<button>About</button>
</Link>
<Link to="/profile">
<button>Profile</button>
</Link>
{authenticated ? (
<LogoutButton logout={logout} />
) : (
<Link to="/login">
<button>Login</button>
</Link>
)}
{authenticated ? (
<></>
) :
(signUpCompleted ? <></> :
<Link to="/register">
<button>Register</button>
</Link>
)
}
</header>
const authenticated = user != null;
부분이 가장 중요하다.
authenticated
를 통해 로그인 상태를 확인하며,
authenticated == true
이기 때문에, 조건 연산자를 통해서 로그인이 되었을 경우, 회원가입이 나타나도록 만들어 주었다.
회원가입도 위의 authentiacted
와 유사하게 만들어졌으며,
로그인/ 로그아웃은 login, logout이 관여한다.
<hr />
<main>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route
path="/register"
render={props => (
<RegisterForm authenticated={authenticated} signUpCompleted={signUpCompleted} {...props} />
)}
/>
<Route
path="/login"
render={props => (
<LoginForm authenticated={authenticated} login={login} {...props} />
)}
/>
<AuthRoute
authenticated={authenticated}
path="/profile"
/*"render={props" 를 쓰는 이유
컴포넌트에 props를 넘기기 위해 사용한다 */
render={props => <Profile user={user} {...props} />}
/>
</Switch>
</main>
</Router>
);
}
export default App;
Route
: 컴포넌트의 속성에 설정된 url과 현재 경로가 동일하면 컴포넌트 혹은 함수를 불러온다.
<Route path="/" component={Sample}/>
과 같이 사용하지만, <Route render={props=>(<Sample {...props}/>
import React, { useState } from "react";
import { Redirect } from "react-router-dom";
function LoginForm({ authenticated, login, location }) {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const onSubmit = (e) => {
e.preventDefault();
try {
login({ email, password });
} catch (err) {
alert("Failed to login");
setEmail("");
setPassword("");
}
};
const { from } = location.state || { from: { pathname: "/" } };
if (authenticated) return <Redirect to={from} />;
return (
<form onSubmit={onSubmit}>
<h1>Login</h1>
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
type="text"
placeholder="email"
/>
<input
value={password}
onChange={(e) => setPassword(e.target.value)}
type="password"
placeholder="password"
/>
<button type="submit" >Login</button>
</form>
);
}
export default LoginForm;
Redirect
: 페이지를 다른 주소로 강제 이동시키는 것
여기서는 이 부분이 가장 중요하다.
const { from } = location.state || { from: { pathname: "/" } };
if (authenticated) return <Redirect to={from} />;
authenticated
는 login에 값이 들어가면 App.js의 user state값이 변동되면서 true
로 바뀌기 때문에, pathname: "/" 주소로 Redirect가 된다.
import React from "react";
import { withRouter } from "react-router-dom";
function LogoutButton({ logout, history }) {
const onClick = () => {
logout();
history.push("/");
};
return <button onClick={onClick}>Logout</button>;
}
export default withRouter(LogoutButton);
history.push()
: 특정 경로로 이동
withRouter
: 라우트가 아닌 컴포넌트에서 라우터에서 사용하는 객체로, location, match, history 를 사용하려면, withRouter라는 HoC를 사용해야 한다.
로그아웃 버튼 클릭 시, logout()을 통해 user state값이 비워지고, / 경로로 이동하게 된다.
import React, { useState } from "react";
import { Redirect } from "react-router-dom";
import { users } from '../auth/auth';
function RegisterForm({ authenticated, history, location, signUpCompleted }) {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [text, setText] = useState("");
const onSubmit = (e) => {
e.preventDefault();
try {
users.push({ name, email, password })
runTasks();
signUpCompleted(true)
} catch (err) {
alert("Failed to register");
setEmail("");
setPassword("");
}
};
function loading(num) {
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const result = num + 1;
if (result > 5) {
const e = new Error('over loading');
return reject(e);
}
resolve(result);
}, 500)
});
return promise;
}
async function runTasks() {
try {
let result = await loading(0);
setText('[1/4]회원가입중.');
await loading(result++);
setText('[2/4]회원가입중..');
await loading(result++);
setText('[3/4]회원가입중...');
await loading(result++);
setText('[4/4]회원가입 완료 !');
await loading(result++);
history.push("/");
} catch (e) {
console.log(e);
}
}
const { from } = location.state || { from: { pathname: "/" } };
if (authenticated) return <Redirect to={from} />;
return (
<form onSubmit={onSubmit}>
<h1>Register</h1>
<input
value={name}
onChange={(e) => setName(e.target.value)}
type="text"
placeholder="name"
/>
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
type="text"
placeholder="email"
/>
<input
value={password}
onChange={(e) => setPassword(e.target.value)}
type="password"
placeholder="password"
/>
<button type="submit">Register</button>
<div>{text}</div>
</form>
);
}
export default RegisterForm;
signUpCompleted
를 통해 회원가입이 완료되면 회원가입 버튼을 없애 주는 기능을 넣었다.history.push("/");
를 통해 메인 페이지로 전환된다.import React from 'react'
const Profile = ({ user }) => {
const { email, password, name } = user || {};
return (
<div>
<h1>{name}'s Profile</h1>
<div>Email: {email}</div>
<div>Password: {password}</div>
</div>
)
}
export default Profile
프로필 컴포넌트는 인증 후에만 접근 가능하며, 이는 AuthRouter.js에서 자세히 다룰 예정이다.
export const users = [
{ email: "a@a.a", password: 'aaa', name: 'A' },
{ email: "b@b.b", password: 'bbb', name: 'B' },
{ email: "c@c.c", password: 'ccc', name: 'C' },
];
export function signIn({ email, password }) {
const user = users.find(
(user) => user.email === email && user.password === password
);
if (user === undefined) throw new Error();
return user;
}
사용자 정보가 저장된 users 객체와,
users의 정보와 로그인시에 입력된 정보와 올바른지 비교하는 signIn
함수가 들어있다.
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
const AuthRoute = ({ authenticated, component: Component, render, ...rest }) => {
return (
<div>
<Route
{...rest}
render={(props) =>
authenticated ? (
render ? (
render(props)
) : (
<Component {...props} />
)
) : (
<Redirect
to={{ pathname: "/login", state: { from: props.location } }}
/>
)
}
/>
</div>
)
}
export default AuthRoute
상당히 복잡하게 느껴질 수도 있지만 순서대로 살펴보면 생각보다 간단하다.
1. <Router>
는 렌더링할 컴포넌트를 받는다.
2. authenticated
가 참인 경우, render
(컴포넌트에서 props 전달)인 경우와 일반 component
인 경우를 나누어 렌더링 해준다.
3. authenticated
가 겨짓인 경우, <Redirect>
를 통해 로그인 경로로 이동시킨다.
import React from 'react'
const About = () => {
return (
<div>
<h1>About</h1>
<p>How am I?</p>
</div>
)
}
export default About
import React from 'react'
const About = () => {
return (
<div>
<h1>About</h1>
<p>How am I?</p>
</div>
)
}
export default About
css를 넣지 않았으며, 다음과 같이 동작한다.
5개의 링크(버튼)가 존재한다.
추후, 이를 모두 개선할 예정에 있다.
이해를 돕기 위해서
전체 코드가 있는 깃 저장소: https://github.com/OseungKwon/practice-react/tree/main/user_confirm