원티드에서 진행하는 프리온보딩 인턴십의 기본 교육 과정을 마무리했다.
아직 커리어 코칭 기간이 남아있지만
지난 4~5주간 교육을 듣고 기업과제를 수행하면서 느겼던 그리고 공부했던 것들을 정리하는 시간을 가져볼까 한다.
첫 시간에는
- 간단한 수업 오리엔테이션
- 협업 프로젝트를 수행하기 위한 lint prettier 설정
- 그리고 Github Action을 이용한 CI/CD 구축법
에 대해서 배웠다.
lint 와 prettier 설정에 대해서는 지난 프로젝트를 통해 학습을 한 상태였지만 그때 프로젝트 셋업을 위해 한 번 한 것이기 때문에 이번에 배운 내용을 토대로 (설정하는법 + vsCode 좀 더 잘 다루기)를 할 수 있게 된 것 같다.
+ 추가적으로 이와 관련하여 project starter pack을 만들어 볼 수 있어 좋았다.
팀은 이미 짜여져있었다. 9명의 인원이 함께 했는데 그중 처음부터 안계신 분이 1명 있었다.
뭐 8명도 충분히 각자 다 다른 코드를 작성하기에 오히려 9명보다 좋다고 생각했다.
팀원끼리 디스코드를 통해 1차 만남을 가지고 과제를 위해 몇차례 더 회의를 가지며 노션에 팀 페이지를 만들었다.
그리고 내가 팀장이 되었다.
지난 번에 이어 또 팀장을 했다. 처음에는 하고 싶은 생각이 별로 없었는데 하고나니 인턴십에 대해 좀 더 진지하고 적극적인 자세로 임할 수 있게 해준것 같아 잘했다고 생각이 든다.
마지막으로 끝나기전 과제에 대한 설명이 있었다.
과제는 팀으로 하나의 Best Practice를 만들어 제출하는 것!
대놓고 협업 능력을 기르기위한 과제라고 생각을 했다.
팀원들과 큰 트러블 없이 잘 구현을 마무리하는 것이 팀리드로서의 목표이다!
이미 끝나고 후기를 쓰는 입장에서 이야기를 한다면 큰 무리없이 모든 과제를 잘 마무리 했다. 🙂
인턴십에 참여하기 위해서는 제출해야하는 과제가 있었다.
그중 프론트엔드 지원자는 login 기능과 간단한 todo list를 구현하는 과제를 수행해야했다.
login 기능은 어떤 과제를 수행해도 한번쯤은 만나게 되는 것 같다.
그만큼 당연히 필요한 기능이면서 중요하고 보안에 신경을 써야하는 기능이다.
과제에서 원하는 구현내용은 다음과 같다.
- signup & login 시 조건에 맞는 email , password 유효성 검사
- signup 시 login 페이지로 이동
- login 시 서버로부터 JWT 받아서 localstorage에 저장
- login 시 authentication 관련 라우트 접근시 todo 라우트로 자동 이동
- 반대로 login 안됐을 시 todo 라우트 접근 제한
Authentication은 많이 구현을 해왔던 터라 크게 부담이 없었다.
하지만 라우팅 처리에서 조금 고심을 했다.
login 상태를 localstorage의 토큰 존재여부로 판단하고 각 페이지 접근 시 제한을 두는 식으로 구현
로그인 시 todo 라우트로의 접근 제한은 따로 Private Route를 만들어서 제한을 뒀다.
const PrivateRoute = () => {
const { isLoggedIn } = useContext(AuthContext);
const { setUserToken } = useContext(TodoContext);
useEffect(() => {
if (isLoggedIn) {
const userToken = StorageControl.storageGetter("token");
if (!userToken) return;
setUserToken(userToken);
}
}, [setUserToken, isLoggedIn]);
return isLoggedIn ? <Outlet /> : <Navigate to="/signin" />;
};
export default PrivateRoute;
BUT
로그인 후 login 관련 라우트를 막는데 문제가 있었다.
login , signup page 컴포넌트 내에서 useEffect로 라우트 접근 가능여부를 판단하고 제한을 두려한것
이렇게 하면 어쩔 수 없이 login 과 signup 컴포넌트에 일단 들어가야하고 그렇게 되면 아주 찰나의 시간이지만 유저에게 login 과 signup 컴포넌트의 UI가 보이게 된다.
안좋은 UX를 주게 된다
위 Private Route 처럼 라우트 단에서 미리 판단하고 경로를 바꿔줘야 이런 문제가 생기지않는다. 현재 팀으로 제출한
여기서 Private Route 처럼 login PrivateRoute를 만들면 되긴하지만 비슷한 코드가 반복되니 좋은 구조라고 보기 힘들 것 같다.
routing 관련해서 횡단 관심사를 엮어서 생각할 수 있다.
로그인 여부가 전체 라우트를 좌지우지 하고 있으니 전체 라우트를 관리하는 router 컴포넌트를 만들어서 거기서 login 여부에 따를 route 처리를 해주면 되겠다.
react-router-dom의 createBrowserRouter 로 path별 router를 설정하여 route 처리를 다루면 될 듯 하다
아직 구현하지는 않았지만 조만간 구현후 글을 작성하도록 하겠다.
과제에서 구현하기를 바랐던 todo list의 기능은 다음과 같다.
- 서버 api 통신으로 todo list 받아오기
- input 창을 통해 todo 작성해 list에 추가
- todo list 에 수정 삭제 버튼을 이용 수정 삭제
- todo list의 check box로 완료여부 선택
서버와의 통신으로 todo list를 받아와 화면에 보여주고 CRUD 기능을 구현하라는 과제이다.
구현에는 큰 문제가 없었다.
하지만...
팀원들과 회의를 통해 구현한 Best Practice에는
- api 통신 부분
- axios 설정 부분
에 대한 분리는 나름 잘 되었다고 생각한다.
// utils axios.ts
const axiosConfig: AxiosRequestConfig = {
baseURL: baseURL,
timeout: 180000,
withCredentials: false,
headers: {
'Content-Type': 'application/json',
},
}
export const client = axios.create(axiosConfig)
client.interceptors.request.use(config => {
if (!config.headers) return config
const token: string | null = localStorage.getItem('ACCESS_TOKEN')
if (token !== null) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
... 생략
// todo-api.ts 중
import { TodoDto, UpdateTodoDto } from './dtos/TodoDto'
import { client } from '../utils/axios'
export function fetchTodos() {
return client.get('/todos')
}
export function createTodo(todo: TodoDto) {
return client.post('/todos', todo)
}
... 생략
하지만 todo list CRUD 통신을 위한 코드가 컴포넌트에 그대로 구현되어 있다.
function Todo() {
const [todoInput, setTodoInput] = useState('')
const navigate = useNavigate()
const [todos, setTodos] = useState<Itodos[]>([])
const ACCESS_TOKEN = localStorage.getItem('ACCESS_TOKEN')
useEffect(() => {
if (!ACCESS_TOKEN) {
navigate('/signin')
} else {
getTodos()
}
}, [])
const getTodos = async () => {
const response = await fetchTodos()
setTodos(response.data)
}
const createTodoHandler = async (todo: string) => {
const response = await createTodo({ todo })
if (response.status === 201) {
setTodoInput('')
setTodos([response.data, ...todos])
}
}
... 중략
return (... JSX 코드 )
}
코드 관심사 분리에 대해서 다시 생각을 해보니
위 getTodos , createTodoHandler 와 같은 함수의 통신부는 가리는 편이 좋지 않았나 싶다.
커스텀 훅으로 빼도 좋고 아니면 context.api 를 써도 좋지 않았을까? 싶다.
하지만 과제를 바라보는 시점에서 따라서 다를 수 있을 것 같다.
단순히 구현만 생각한다면 현재도 좋은 코드라고 생각한다. 하지만 관심사를 분리하여 컴포넌트에서는 api 통신부는 신경쓰지 않도록 한다면 앞서 말한대로 따로 빼는 것이 좋을 것 같다.
여튼 이래저래 첫 과제를 잘 마무리했다.
여러 팀원들간의 의견 조율과 페어 코딩 및 코드 스타일 맞추기 등등 할게 생각보다 많았다.
후기를 이번 하나로 마무리를 하려했는데 길어질 것 같다... ㅎㅎ
빠르게 다음 후기를 작성하도록 하겠다.
다음부터는 주차별 과제와 학습내용을 중점으로 작성하도록 하겠다.
끄 읕 ~~~