2주 차 과제는 jaranda 기업의 회원가입
, 로그인/로그아웃
, 관리자 페이지
를 구현하는 과제였다.
이번 과제는 분량이 많아서인지 팀원 전체 협업과제에다가 +2일의 추가시간이 주어졌다.
처음에는 7명이서 협업을 하는 것에 어려움이 많을 것이라 생각했지만 매일 팀원들과 각자의 작업 현황을 공유하고 커뮤니케이션을 통해 개발 진행 상황을 정리하여 수월하게 프로젝트를 진행할 수 있었던 것 같다.
프로젝트 내에서 로그인 기능을 맡았는데 로그인 관련 로직을 한곳에서 파악하고 관리할 수 있도록 class
로 설계했다.
실제 로그인은 서버의 데이터베이스에서 데이터를 조회하고 api를 통해 클라이언트에게 결과 값을 넘겨주는 형식이다.
특히 login()
메서드는 서버와 클라이언트가 통신하는 과정을 생각하며 비슷하게 로직을 구현을 해보았다.
// Auth.js
async login(loginData, isAdminRestrict = false) {
this.userList = this.userListStorage.load()
const database = isAdminRestrict
? this.userList.filter((account) => account.auth === authType.ADMIN.name)
: this.userList
const account = database.find((account) => account.email === loginData.id)
const isRegisteredAccount = database.some(
(account) => account.email === loginData.id
)
const isPasswordMatch =
isRegisteredAccount && account.password === loginData.pw
if (!isRegisteredAccount) {
throw new CustomError(errorState.NO_ACCOUNT_REGISTERED)
} else if (!isPasswordMatch) {
throw new CustomError(errorState.PASSWORD_MISMATCH)
} else {
const protectedAccountInfo = {
loginTime: new Date().getTime(),
name: account.name,
access: account.access,
auth: account.auth,
id: account.id,
email: account.email,
}
this.currentAccountStorage.save(protectedAccountInfo)
this.auth = protectedAccountInfo
return protectedAccountInfo
}
}
지금까지 토스트는 팝업 메시지는 toastify 라이브러리 를 사용하였는데
기업 과제는 웬만하면 라이브러리를 사용하지 않는 쪽으로 진행하기로 하여서 직접 구현을 해보기로 했다.
생각보다 어렵지 않아서 놀랐고 무작정 라이브러리만 찾던 나를 반성하게 되었다
// Toast.js
import React, { useState, useEffect } from 'react'
import { createPortal } from 'react-dom'
import styled from 'styled-components/macro'
export default function Toast({ message, isShow, className }) {
const [show, setShow] = useState(null)
useEffect(() => {
if (!isShow) {
setTimeout(() => {
setShow(false)
}, [200])
} else {
setShow(isShow)
}
}, [isShow])
return (
<>
{show &&
createPortal(
<Wrapper
role="alertdialog"
aria-live="assertive"
isShow={isShow ? 1 : 0}
className={className}
>
<Message>{message}</Message>
</Wrapper>,
document.body
)}
</>
)
}
const Wrapper = styled.div.attrs(({ isShow }) => ({
opacity: isShow,
}))`
position: fixed;
top: 15rem;
left: 50%;
border-radius: 1.5rem;
padding: 0.5rem 1rem;
background: rgba(87, 87, 87, 0.9);
transform: translateX(-50%);
opacity: ${({ opacity }) => opacity};
transition: opacity linear 0.2s;
z-index: 10000;
`
const Message = styled.span`
font-size: 1.4rem;
color: rgba(255, 255, 255, 0.9);
`
토스트 컴포넌트에서 isShow props로 virtual DOM에 탈부착 되는 형식으로 구현을 하고서 토스트 컴포넌트가 사라질 때 스르륵 사라지기 위해 transition을 추가하였는데 스르륵 사라지기 전에 이미 DOM에 떨어져 나간 뒤였다.
그래서 토스트 컴포넌트에서 show라는 상태를 만들고 렌더링 조건문을 show 상태로 지정하고서 isShow props가 false가 되면 setTimeout에 임의의 시간을 주고 show 값을 false로 바꿔주는 사이드이펙트를 추가하였다.
지금 와서 생각해 보면 isShow, show 상태 값과 프룹스값 이름이 겹치는 것도 그렇고 setTimeout에 임의의 시간을 지정한 것도 그렇고 문제가 많은 코드인 것 같다.
1. 상태 값을 [show, setShow] => [render, setRender] 이름 변경
2. setTimeout => transitionend event
이렇게 코드를 작성했으면 더 좋았을 것 같다.
ProtectedRoute 컴포넌트는 이름 그대로 component 속성으로 전달 받은 컴포넌트를 래핑하여 보호합니다. render 함수를 활용해 감싼 컴포넌트를 렌더링 합니다.
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import auth from './auth';
const ProtectedRoute = ({component: Component, ...rest}) => (
// 인증 없이 접근 못하도록 구현
<Route {...rest} render={
(props) => auth.getAuth ?
<Component {...props} /> :
<Redirect to={{
pathname: '/',
state: { from: props.location }
}}/>
}>
);
export default ProtectedRoute;
이번에 protected Routing을 구현해보면서 Route 컴포넌트 렌더링 방법으로 component 속성을 사용하는 방법 외에 다양한 방법이 더 있다는 걸 배웠다.
렌더링 방법 | 설명 |
---|---|
<Route component> | URL과 매칭되는 컴포너트를 렌더링 합니다. |
<Route render> 함수 | 인라인 렌더링 또는 래핑 렌더링 시에 사용합니다. |
<Route children> 함수 | 매칭될 경우, 자식 컴포넌트를 렌더링 할 때 사용합니다. |
// 인라인 렌더링
<Route
path="/"
exact
render={() => <div className="Home">홈</div>}
/>
// 래핑 렌더링
const FadeInRoute = (({component: Component, ...rest}) => {
return (
<Route
{...rest}
render={(routeProps) => (
<FadeIn>
<Component {...routeProps} />
</FadeIn>
)}
/>
)
}
<FadeInRoute path="/lab" component={Lab} />
PascalCase
로 작성하기로 했다.PascalCase
,그 외 js 파일은 소문자로 구분 지어서 했으면 더 좋았을 것 같다.