총 2주 반 정도의 프로젝트 기간을 통해 '언성히어로'라는 서비스를 만들었다. 짧지만 기획부터 개발까지 진행하며 많은 것을 배울 수 있는 기간이었고 스스로에 대해서도 많이 깨달은 기간이었다. 그래서 프로젝트, 그리고 프로젝트 기간 동안 느낀 것들에 대해 간단히 정리하고자 한다.
서비스 소개
서비스 명: 언성히어로
해당 서비스는 '말 없는 아버지들의 고민 상담소' 라는 캐치프레이즈를 가지고, 많은 고민을 안고 살아가지만 쉽게 고민을 털어놓지 못하는 아버지들을 위한 자유로운 소통, 상담 그리고 위로받는 공간을 만들기 위해 기획하였다.
언성히어로는,
용기 있는 행동, 자기희생적 행동을 하여 훌륭한 업적을 달성했지만 유명하거나 알려지지 않은 사람’을 뜻하는 합성어
이런 뜻을 가지고 있다. 우리는 여기서 언제나 희생하지만 남들은 존재를 알아채지 못하는 배트맨이 생각났고 메인 테마를 배트맨으로 정하였다.
배포 사이트: https://unsunghero.netlify.app/
언성 히어로
🦇 말 없는 아빠들의 고민 상담소
unsunghero.netlify.app
깃헙: https://github.com/prgrms-fe-devcourse/FEDC3_UnsungHero_Donggeun
GitHub - prgrms-fe-devcourse/FEDC3_UnsungHero_Donggeun: [[Unsung Hero]] 말 없는 아빠들의 고민 상담소
[[Unsung Hero]] :bat: 말 없는 아빠들의 고민 상담소. Contribute to prgrms-fe-devcourse/FEDC3_UnsungHero_Donggeun development by creating an account on GitHub.
github.com
기획 단계
1) 주제
본격적인 프로젝트 기간에 들어가기 앞서, 약 일주일간 주제에 대한 브레인 스토밍을 진행하였다. 총 12개의 아이디어가 나왔고 함께 모여서 투표로 정하기로 했다. 하지만 투표 당일 날 한 팀원이 '이런 건 어때요?'라고 제시한 아이디어가 기존의 아이디어보다 와닿았고 다른 팀원들이 마음에 들어 했다. 그렇게 결정된 주제가 '아빠들을 위한 상담' 이었다.
2) 역할
프로젝트 역할
PM
깃헙 관리
문서화
디자인
프로젝트 초기 세팅
역할은 개발 역할 이외에도 프로젝트 전반적인 역할을 나누어 맡았고 그중 내가 맡은 것은 디자인이다.
디자인을 할 줄 아는 것도 아니었고 피그마를 익숙하게 쓸 수 있는 것도 아니었지만 그래도 관심이 아예 없던 분야는 아니어서 재미있게 할 수 있을 것 같았다. 팀원들도 디자인을 맡기에 꺼려하는 것 같아서 선뜻 나섰었다.
개발 관련 역할
회원가입, 로그인
포스트 검색 및 리스트 보기
사용자 정보 관련 기능
포스트 CRUD
좋아요 및 댓글 CRUD
알림
이렇게 주어진 기본 요구사항을 중심으로 기능을 나누었고 나는 여기서 '사용자 정보 관련 기능'을 맡았다. 크게 유저 정보 조회, 유저 정보 수정이 있었고 어떤 식으로 구현할지 생각했을 때 어느 정도 상상이 가능해서 할 수 있을 거라 생각해서 이 부분을 맡았다.
3) 폴더 구조
폴더 구조는 대부분의 팀원이 기존에는 components, hooks, page.. 이런 식으로 나누는 폴더구조를 사용했다. 그런데 멘토님과의 커피챗에서 기능으로 나누는 폴더 구조를 선호하신다고 하신 것이 생각났고 이번 프로젝트는 기능별 폴더구조를 경험해 보자 라는 마음으로 진행했다. 그래서 초기에 나눈 폴더구조는
public
L index.html
L manifest.json
src
L alarm (알람과 관련된 모든 파일이 들어감)
L auth (로그인, 인증 관련.)
L comment (댓글 기능관련)
L post (작성된 글 관련)
L search (검색 기능관련)
L styles (프로젝트 전체적인 스타일)
L user (사용자 본인에 대한 기능들)
App.tsx
Router.tsx (라우터 설정)
index.tsx
이런 식으로 분류하였고 마지막까지 이 구조를 거의 유지하였다.
4) 사용한 기술스택
기술 스택은 기획 첫날부터 의논하면서 대략적으로 정했지만 이후 개발을 진행하면서 필요에 의해 라이브러리를 추가하면서
프로젝트에는 이렇게 사용되었다.
기술스택을 정하면서 그냥 써보자~라고 하는 것보다 이 라이브러리가 왜 필요한지, 우리 프로젝트에 적합하지에 대해 고민하려고 팀원들과 노력하였다.
가장 중요한 것은 타입스크립트였다. 팀원들이 대부분 타입스크립트에 대한 경험이 적거나 아예 없는 상태였고 기본 개념만 아는 상태였다. 따라서 프로젝트에 적용해 보면서 사용하는 방법에 대해 익숙해지는 것이 목표였고 그래서 타입스크립트를 프로젝트에 도입하기로 결정했다.
5) 와이어프레임 & 유저 스토리
디자인이라는 역할을 맡게 되었는데 와이어프레임이라는 단어조차 처음 들어봐서 와이어프레임은 언제 제작되어야 하는가? 에 대한 고민이 있었다. 그러던 중에 기획서에 제작하면서 와이어프레임이 추가되면 좋겠다는 의견이 있어서 개발 들어가기 전에 급히 팀원과 함께 와이어프레임을 제작하게 되었다.
현재 디자인과 다소 다른 부분도 많지만 대략적인 와이어프레임을 제작하면서 다른 팀원은 페이지에 대한 유저스토리를 작성하였다. 디자인을 하면서 고민되는 부분은 2안을 만들어두어 팀원들과 함께 의논하여 결정할 수 있도록 하였다. 이렇게 대략적인 디자인과 유저스토리를 정해놓고 나니 어떤 식으로 서비스가 만들어지겠구나라고 감을 잠을 수 있어서 좋았고 개발에 있어서도 이런 컴포넌트들이 필요하구나 하고 미리 생각해 둘 수 있어서 좋았다.
피그마를 제대로 써본 건 거의 처음이라 피그마에 대한 기능을 잘 모르고 시작하다 보니 불필요하게 시간을 쓰거나 반복작업을 하는 게 많았던 것 같다. 만약 다음에 또 피그마를 쓸 일이 있다면 효율적으로 사용하는 방법에 대해 더 찾아보고 싶다.
개발
처음에 내가 맡았던 부분은 '사용자 관련 기능'이었고 이후에 다른 작업들 하면서 추가로 다른 작업들을 진행했다.
유저 정보 확인 및 수정 기능
팔로워, 팔로잉 목록 확인 기능
팔로우, 팔로잉 커스텀 훅 제작
전체 레이아웃 구현
공통 컴포넌트 제작
전체 컴포넌트 css 수정 및 통일
프로젝트를 진행하면서 크게 이 정도의 작업에 참여하였고 조금씩 다른 부분도 수정하거나 리팩토링하는데 참여했던 것 같다.
유저 정보 확인 기능
유저 정보 확인 페이지에서는 api에서 받아온 정보를 가공해서 사용하면 되는 부분이라 크게 어려움은 없었다. 데스크탑 버전에서는 페이지네이션, 모바일버전에서는 무한스크롤을 적용하면서 뿌려주는 데이터의 형태가 다르다 보니 모바일버전의 컴포넌트를 만들어서 진행했다. 이후 멘토님의 피드백도 그렇고 지금 생각해 보면 그냥 데스크탑에서도 무한스크롤로 적용해도 좋았을 것 같다는 생각이 든다.
하지만 자주 경험해보지 못했던 페이지네이션에 대해 다시 한번 공부해 볼 수 있어서 좋았다.
유저 정보 수정 기능
유저 관련 페이지에서 가장 오래 걸리고 고민했던 부분이 프로필이미지, 커버 이미지 수정 부분이었다. 일단 api를 해석하는데 시간을 조금 투자했었다. api의 body로 어떤 이미지 파일을 넘겨줘야 하는지 고민했었다. 그래서 binary, base64, 이미지 주소 등 여러 가지를 시도해 봤고 이후 팀원의 도움으로 formData로 파일 그 자체를 넘기면 되는 api였다는 것을 알게 되었다.
기능을 구현하면서 처음에는 수정할 이미지를 선택하면 바로 api가 통신되어 저장되도록 하였다. 하지만 저장 버튼이 따로 있는 상황에서 저장을 누르지 않고 뒤로 가면 기존의 변경 전 이미지가 나오는 게 사용자에게 더 익숙하지 않을까?라는 생각을 했고 저장 버튼을 누르면 api 통신을 할 수 있도록 로직을 변경하였다.
const imgFileRef = useRef<IImage>({
cover: '',
profile: '',
});
const handleChangeImg = (e: React.ChangeEvent<HTMLInputElement>, type: string) => {
if (e.currentTarget.files !== null) {
...
const formData = new FormData();
const cover = type === 'profile' ? 'false' : 'true';
formData.append('isCover', cover);
formData.append('image', file);
imgFileRef.current[type as keyof IImage] = formData;
setimgFiles(imgFileRef.current);
}
};
커버이미지나 프로필이미지에 대한 api가 구분되어 있지 않았고 유저 정보를 한 번에 수정하는 api만 있어서 useRef를 사용해서 값을 저장한 다음 부모 컴포넌트에서 props로 전달한 setImgFiles를 통해서 api로직이 있는 부모 컴포넌트의 state에 해당 이미지 정보 객체를 저장한다. 그 뒤 저장버튼을 누르는 경우에 api 통신을 할 수 있도록 하였다.
const handleChangeUserInfo: SubmitHandler<IFormValue> = async ({ fullName, password }) => {
setIsLoading(true);
if (dirtyFields.fullName) {
if (!checkUserName(fullName)) return;
await changeUserFullName(fullName);
}
if (dirtyFields.password) await changeUserPassword(password);
for (const formdata of Object.values(imgFiles)) {
if (formdata instanceof FormData) {
await changeUserImage(formdata);
}
}
setIsLoading(false);
navigate(`/user/${id}`, { replace: true });
};
유저 정보 수정 폼은 react-hook-form을 이용해서 개발했다. 처음에는 폼이 많지 않아서 라이브러리를 쓰지 않아도 되겠다는 생각으로 라이브러리를 쓰지 않고 개발했었는데 팀원들이 라이브러리를 통해서 좀 더 코드를 깔끔하게 작성할 수 있을 것 같다는 의견을 주어서 적용하였다.
라이브러리를 추후에 도입하면서 처음부터 사용하면 시간을 절약할 수 있지 않았을까? 하는 아쉬움은 있었지만 라이브러리를 쓰지 않았을 때를 구현해 봄으로써 라이브러리를 썼을 때와의 차이점을 명확히 느낄 수 있었고 라이브러리도 이런 식으로 구현했을까? 하는 궁금증을 가질 수 있어서 오히려 좋은 경험이었다고 생각한다.
해당 정보수정페이지에서 가장 중요하게 생각한 부분은 유저네임 폼과 비밀번호 폼이 있을 때 '유저가 수정하지 않은 내용에 대해서는 api를 호출하지 않는다.'였다. 그래서 유저의 입력이 없는 경우, 혹은 원래 데이터와 같은 경우에는 api 호출을 안 하고 싶었고 라이브러리를 사용하지 않았을 땐 이 부분에서 코드가 조금 지저분해졌었다. 하지만 react-hook-form을 도입하니 dirtyFields라는 좋은 기능을 통해서 defalutValue와 비교해서 변경된 경우에는 api 함수를 호출할 수 있도록 할 수 있었다.
이 부분에서 아쉬운 점은 api 함수를 호출할 때 모두 async, await를 사용해서 순서대로 진행된다는 점이다. promise.all 등을 통해서 한 번에 통신되도록 변경할 수 있을 것 같아서 리팩토링 예정이다.
유저네임의 경우 다른 닉네임과 중복확인을 하고, 비밀번호의 경우 기존 비밀번호 validate를 지켰는지 확인한다. 해당 부분은 다른 팀원이 작성한 회원가입 부분에도 동일한 로직이 있어서 해당 부분을 사용해서 커스텀 훅으로 만들었고 유저 정보 수정페이지에도 적용하였다.
팔로우, 언팔로우 기능
팔로잉 목록에서는 내가 팔로우하고 있는 계정의 목록을 확인할 수 있으며 바로 언팔로우가 가능하고 계정을 클릭할 경우 해당 계정의 정보 페이지로 이동하게 된다.
팔로워 목록에서는 나를 팔로우하고 있는 계정의 목록을 확인할 수 있으며 바로 팔로우, 언팔로우가 가능하고 계정을 클릭할 경우 해당 계정의 정보 페이지로 이동하게 된다.
팔로우 관련해서 기존에 팀원이 컴포넌트로 만들어둔 로직을 활용하여 커스텀훅으로 제작하였다.
팔로우, 언팔로우 버튼을 만들 때 내가 팔로우하고 있는 계정인지 데이터를 확인하고 그에 따라 어떤 버튼을 보여줄지 정하는 로직을 훅을 사용하는 곳에서 하는 것보다 커스텀 훅 내에서 처리해서 바로 제공해 주는 것이 좋을 것 같다고 생각했다.
const followButton = (id: string) => {
if (id === userIdContext?.userId) return;
const following = loginUserData?.following.map((user) => user.user);
return following?.includes(id) ? (
<Button
text={'언팔로우'}
color={'white'}
onClick={(e) => handleClickUnFollow(e, id)}
width={6.25}
height={1.875}
style={{ marginLeft: 'auto' }}
/>
) : (
<Button
text={'팔로우'}
color={'default'}
onClick={(e) => handleClickFollow(e, id)}
width={6.25}
height={1.875}
style={{ marginLeft: 'auto' }}
/>
);
};
그래서 해당 로직을 통해 내 팔로잉 목록을 api에서 받아온 정보에서 가공하고 그 목록에 해당 유저가 있으면 언팔로우 버튼을, 없으면 팔로우 버튼을 보여주도록 하였고 커스텀 훅을 사용하는 곳에서는 함수에 유저 id만 넘겨주면 되도록 설계하였다.
전체 레이아웃 구현
레이아웃의 경우 처음에 맡았던 역할은 아니었는데 개발 초반 단계에서 레이아웃을 먼저 잡아야 하지 않을까라는 이야기가 나왔다. (이 부분을 기획단계에서 챙기지 못한 점이 아쉽다.) 그래서 내가 맡은 유저 정보 기능을 빨리 끝낼 수 있을 것 같다고 판단하여 해당 작업을 맡아서 했다. 크게 채널, 헤더 컴포넌트와 전체 레이아웃을 잡을 컴포넌트로 제작하였다. 그리고 추후에 미디어쿼리 작업을 진행하면서 하단 Navbar 컴포넌트를 추가하였다.
<>
<Header />
<Wrapper>
<MainWrapper>
<SidebarBackground onClick={() => setMenuOpen(false)} menuOpen={menuOpen} />
<Channels menuOpen={menuOpen} />
<Main>
<Outlet />
</Main>
</MainWrapper>
<Navbar menuOpen={menuOpen} setMenuOpen={setMenuOpen} />
</Wrapper>
</>
모든 컴포넌트에 공통적으로 헤더와 채널이라는 레이아웃을 적용하기 위해 react-router의 nested routes 기능을 적용하였다. 그래서 컴포넌트들이 `<Outlet />` 태그 부분에 들어가도록 라우터를 설정하였다. 처음에는 이 기능을 모르고 children을 props로 받아서 해당 부분에 렌더링 되도록 했는데 이후 리액트 라우터에 대해 찾아보면서 nested routes 기능을 알게 되었다. 원래는 다른 곳에 적용하려고 알아보던 거였는데 레이아웃에도 children 대신에 적용할 수 있을 것 같아서 적용하였고 props로 children을 전달하는 것보다 리액트 라우터의 기능을 사용하여 구현된 점이 좋은 것 같다.
미디어쿼리의 경우 처음에는 애니메이션을 안 넣은 상태에서 state 값에 따라 display: none을 사용해서 채널을 숨겼는 데 사용하려는 transition의 경우 원래 위치를 기준으로 애니메이션이 동작하는 것이기 때문에 기본 상태가 없다면 적용되지 않는 것이었다. 따라서 display를 변경하는 것이 아니라 translateX를 채널을 안 열었을 때는 -100%로 숨기고 채널을 열면 0%로 만들어서 제자리로 오도록 하였다.
const Wrapper = styled.div<IProps>`
...
@media (max-width: ${({ theme }) => theme.media.moblie}) {
...
transform: translateX(${({ menuOpen }) => (menuOpen ? '0%' : '-100%')});
}
`;
공통컴포넌트
공통컴포넌트에서 작업한 부분은 버튼 컴포넌트, 페이지네이션 컴포넌트, 아바타 컴포넌트이다.
버튼 컴포넌트의 경우 prop로 넘겨주는 color에 따라 배경과 테두리 색이 결정된다. color가 default인 경우 배경과 테두리가 메인 색깔인 초록색이 되고, white인 경우 배경이 하얀색, 테두리가 초록색이 된다. 그리고 delete인 경우 배경과 테두리 모두 빨간색으로 된다. 만약 버튼에 hover를 하는 경우 white는 초록색으로 default, delete는 하얀색으로 변경된다.
const ButtonType: IBtnType = {
default: '#52D2A4',
white: '#FFFFFF',
delete: '#FF1F1F',
};
const Btn = styled.button<IProps>`
...
background-color: ${({ color }) => ButtonType[color as keyof IBtnType]};
color: ${({ color, theme }) => (color === 'white' ? theme.colors.black : theme.colors.white)};
&:hover {
background-color: ${({ color, theme }) =>
color === 'white' ? theme.colors.primary : theme.colors.white};
color: ${({ color }) => ButtonType[color as keyof IBtnType]};
}
이런 식으로 색깔을 객체로 지정해 둔 후 styled component의 props를 통해서 값이 변경될 수 있도록 하였다.
페이지네이션의 경우 사용하는 곳에서 limit, offset를 설정해서 넘겨주면 그 값을 받아서 버튼을 생성할 수 있도록 하였다. 만약 현재 페이지가 처음에서 3개보다 적거나, 끝에서 3개보다 적어도 버튼이 동일하게 5개가 보이도록 작업하였다.
아바타 컴포넌트의 경우 이미지 주소, width, height를 받아와서 그려줄 수 있도록 작업하였다. 이 컴포넌트를 만든 이유는 유저가 이미지를 따로 설정하지 않은 경우 기본 이미지를 설정했어야 하는데 서버에서 따로 설정되어 있지 않아서 프론트단에서 설정했어야 했다. 이때 프로필을 사용하는 곳곳에서 해당 기본 이미지를 사용하는 것보다 공통 컴포넌트로 만들어두는 게 편리할 것 같다고 생각해서 만들게 되었다.
디자인
디자인은 기획단계에서부터 계속 고민했었다. 기본틀은 와이어프레임 형태를 가져갔지만 세부적인 디자인은 개발하면서, 실제로 css 적용하면서 많이 변경되었다. sns라는 주제에 맞게 좀 더 트렌디한 디자인을 하고 싶은 마음도 있었다. 하지만 서비스의 대상이 청년층 - 중장년층의 아버지이고 해당 대상의 경우 이러한 전자기기의 사용, 사이트의 활용이 능숙하지 않을 것 같았다. 따라서 현재 유명한 sns의 디자인보다는 나이 많은 분들이 좀 더 익숙하게 느끼는 '커뮤니티' 적인 느낌을 주는 것이 좋다고 판단하였고 커뮤니티와 sns를 섞어서 디자인하게 되었다. 서비스를 개발할 때 이 기능이 좋은가 안 좋은가, 디자인이 예쁜가 안 예쁜가도 중요하지만 이 기능이, 디자인이 우리 서비스에 필요한가? 적절한가? 에 대해 더 고민할 필요가 있다는 것을 새삼 깨닫게 되었다.
디자인을 맡으면서 제대로 피그마를 처음 써봤고 프로토타입이라는 것도 처음 만들어봤다. 프로토타입으로 페이지가 어떻게 움직이고 돌아가는지 대략적으로 파악할 수 있어서 좋았다. 이 기능은 우리 서비스보단 움직임이 중요하거나 애니메이션이 많은 경우에 유용하게 사용할 수 있을 것 같았다.
현재 제작된 프로토타입에서도 개발 과정에서 디자인이 더 변경되었고 그 부분은 시간이 없어서 피그마에 적용하지 못한 것이 아쉽다. 피그마를 잘 다루지 못하다 보니 이런 변경사항이 생기면 하나하나 다 수정해야 하는 것이 시간을 많이 잡아먹는 요소 중 하나였던 것 같다.
이러한 전체적인 디자인 외에도 로고를 제작하고 폰트를 결정하는 일을 맡아서 했다. 디자인이라는 직책을 가지다 보니 css나 디자인적인 요소에 더 신경을 쓰려고 노력했다.
로고의 경우 캔바로 제작하였고 간단한 도안을 10개 정도 만들어서 팀원들과 의논하여 결정하였다. 폰트 역시 기존에는 구글 웹폰트를 사용했지만 디자인적인 면에서나 가독성 면에서 적절하지 않다고 판단하였고 우아한 형제들, 지마켓 폰트를 사용하였다. 현재 프로젝트에는 폰트 파일 자체를 저장해 놓고 사용하고 있는데 이후에 성능을 위해 웹폰트로 변경할 수 있는 것은 변경하고 싶다.
아쉬운 점
1) 폴더구조
'기능별로 폴더를 나눈다.' 로만 생각해서 폴더 하나에 모든 파일이 들어가 있다. 그러다 보니 어떤 게 컴포넌트고 어떤 게 훅인지 잘 구별이 안된다는 피드백을 받았고 현재 폴더에서 deps를 줘서 컴포넌트, 훅, page를 구분할 필요가 있다는 생각이 들었다.
2) 공통컴포넌트, theme 설정이 미리 이루어지지 않은 점
개발에 대한 경험이 많이 없다 보니 개발 전에 진행되면 효율적인 점들에 대해 생각하지 못했다. 그래서 전체 레이아웃도 마찬가지로 개발을 시작하고 난 후에 필요성을 느꼈고 공통컴포넌트도 마찬가지였다. css에 필요한 공통 컴포넌트, theme 설정이 팀원들의 css 작업과 동시에 시작했고 결과적으로 내가 공통컴포넌트를 구현한 것과 별개로 팀원들도 해당 부분을 각자 구현한 상태였다. 그래서 공통 컴포넌트 구현 & 각자 해당 부분 구현 -> 각자 구현한 부분 공통 컴포넌트로 변경 이렇게 진행되면서 일을 두, 세 번 하고 있는 것 아닌가?라는 생각이 들었다.
theme 역시 마찬가지로 사용한 컴포넌트도 있고 아닌 컴포넌트도 있어서 지금 당장은 괜찮지만 다크모드를 구현하자는 이야기가 나왔을 때 하나하나 다시 설정해 주면서 확인했어야 해서 유지보수에 매우 안 좋았구나 라는 생각이 들었다. 이후에 프로젝트를 진행하게 된다면 이러한 부분을 꼭 미리 챙기고 싶다.
3) 타입스크립트를 제대로 활용하지 못한 점
타입스크립트에 대해 완전히 이해하지 못한 채 개발에 들어가게 되면서 에러가 뜨는 부분을 막기 급급했던 것 같다. 그래서 이유를 모르고 쓰는 부분도 있었고 as를 통해 타입을 강제로 확정하는 부분도 많았다. 그래서 타입스크립트의 장점을 이해하긴 했지만 이게 정말 편리한가?? 에 대한 의문이 있었는데 프로젝트를 마감한 후에 다른 분께서 어떤 식으로 타입스크립트를 사용하면 좋을지 예시를 알려주셨고 그것을 보고 충격을 받았다. 우리가 개발에 사용한 타입스크립트는 완전 기초만 사용한 거구나 하고 느꼈다. theme의 경우도 상수로 설정하면 사용할 때 어떤 값인지 더 편하게 알 수 있다는 것을 알게 되었고 이래서 타입스크립트를 좋아하는구나 하고 느꼈던 것 같다. 그래서 추후 리팩토링, 그리고 이후 공부, 프로젝트에서는 타입스크립트를 좀 더 장점을 살려서 사용해보고 싶다.
4) axios, 비동기 처리
개발 초반 단계에서 axios 호출 함수에서 반환하는 값은 promise<pending> 상태라는 것을 깨닫게 되었다. 이 부분에서 삽질과 고민을 굉장히 많이 했었는데 자바스크립트의 api 통신이나 비동기처리에 대해서 어느 정도 알고 있다고 생각했지만 아직 모르는 부분이 있구나라고 스스로 깨닫게 된 계기였다. 그래서 자바스크립트의 비동기처리에 대해 다시 제대로 공부하는 시간을 가져야겠다고 생각했다.
'개발 공부 > 프로젝트' 카테고리의 다른 글
우리들의 모임, WuMo 프로젝트 회고 (0) | 2023.03.29 |
---|---|
[데브코스]노션 클로닝 프로젝트 회고 (6) | 2022.11.17 |
To Do List 사이트 만들기 프로젝트 (0) | 2022.08.09 |
노마드 코더 바닐라 JS 크롬 앱 만들기 졸업작품 (0) | 2022.08.09 |