지난 포스팅에서 c드라이브에서 어플리케이션을 수행하면 앞으론 잘 동작될거라고 생각했던 나의 핑크빛 리액트 미래는 산산히 무너지고 말았다.

아니 이 망나니 녀석 왜 강사님 앞에선 됐으면서 나 혼자 남으니까 또 안되는거냐. 이거 그거 아니냐고 컴퓨터 수리 기사님 오면 컴퓨터가 마치 아팠던 적 없는 척 하는 그거. 하필 12월 31일 강사님 마지막 근무시간까지 나한테 할애를 하면서 고쳐낸건데 1월 1일이라 도움의 손길을 요청할 수도 없는 노릇이었다.

진짜 열받네. 너는 형벌이다. 포맷.

결국 포맷 엔딩루트를 타고 "vs code와 nodejs만 설치해서 확인해보면 리액트가 완전히 고쳐졌겠지?" 라는 나의 망상은 나를 철저하게 깨부셔주었다.

포맷의 형벌

npm : 이 시스템에서 스크립트를 실행할 수 없으므로 C:\Program Files\nodejs\npm.ps1 파일을 로드할 수
없습니다. 자세한 내용은 about_Execution_Policies(https://go.microsoft.com/fwlink/?LinkID=135170)를
참조하십시오.
위치 줄:1 문자:1
+ npm init
+ ~~~
    + CategoryInfo          : 보안 오류: (:) [], PSSecurityException
    + FullyQualifiedErrorId : UnauthorizedAccess

🤦‍♀️네..? 이게 무슨 소리요 의사양반, 포맷하면 다 괜찮아 지는거 아니였소?     🤷‍♂️네 아닙니다.

오류 코드를 구글링해서 해결 방법을 찾아보니 nodejs/npm.ps1을 삭제하면 해결이 될거라는 어느 개발자 블로그를 참고해 해당 파일을 찾아나섰다.

그런데 해당 디렉토리에 들어가서 찾아봐도 npm.ps1이라는 파일은 찾아 볼 수 없었고, powershell로 숨겨진 파일 목록까지 보여주는 ls -al을 작성해봤지만 powershell에서 la -al 명령어를 인식하지 못했다. (자꾸 빨간색으로 어쩌구 저쩌구 하는데 list는 보여주지 않음)

여기서 2차 열받. 포맷하는것도 지금 큰 마음 먹고 한거구만 왜 이런 시련을 주는것이죠..?

재포맷간다. (두번째 포맷)

그래서 두번째 포맷을 하고 기본적인 셋팅(vscode, git 설치)만 해둔 뒤 날이 밝기를 기다렸고, 9시 땡 하자마자 강사님께 찾아가 나의 안타까운 사정을 읍소했다.

강사님과 줌에서 만나 화면 공유를 하고 node 설치부터 같이 했다.

원래는 c\program files\nodejs 로 설치되는 경로를 c\nodejs로 변경한 뒤 설치를 진행했고, C 드라이브 하위 폴더로 workspace라는 디렉토리를 만들어 이 안에서만 리액트 작업을 하기로 했다.

VSCode 오류 : 이 시스템에서 스크립트를 실행할 수 없으므로 C:\Users\...파일을 로드할 수 없습니 다. 
자세한 내용은 about_Execution_Policies(https://go.microsoft.com/fwlink/?LinkID=135170)를 참조하십시오.

LinkID=135170 ? 포맷 처음 했을 때 봤던 오류에서 참조하라는 사이트 주소랑 똑같은데라는 생각에 강사님께 첫 포맷에서 되지 않았던 npm.ps1 파일 삭제 실패 이슈를 말씀드렸고 git bash와 powershell을 관리자 권한으로 실행해서 powershell에서 ls -al이 되지 않던걸 git bash에서 명령어를 입력해 숨겨진 npm.ps1 파일을 찾았다.

그리고 rm -rf npm.ps1 / git bash에서 한다면 sudo rm -rf npm.ps1을 해줬고, npx.ps1도 똑같이 삭제 해 줬다.

그리고 테스트를 해봤더니 결과는 성.공.적.

하지만 지난번에 내가 다른 디렉토리를 만들고 리액트를 실행했을 때 실패했으므로 강사님과 줌을 하는 동안에 확인하고 싶었다. (강사님 계실때만 잘되고 나 혼자할 때 안되면 안되니까.)

그래서 c\workspace에 새로운 디렉토리를 만들고 vs code를 열어 터미널에 명령어를 입력했다.

npm init react-app .

설치가 되는듯 하면서 마지막엔 결국 erorr가 떴는데 강사님께서 당황하지 말고

npm i web-vitals
//i는 install의 줄임이다.

해당 명령어를 실행해보라는 말씀을 해주셨다.

그랬더니 터미널창은 다시 바쁘게 돌아가고 완료가 된 후에 

npm run start

를 해봤더니

이렇게 보니 반갑다 친구야

잘 실행이 됐다.

강사님께 왜 npm i web-vitals를 해줘야 하는것이냐고 여쭤봤는데 이건 내 컴퓨터도, 윈도우 문제도 아닌 오픈 소스의 문제라고 하셨다. 저 명령어는 오픈 소스에서 불안정한 이슈를 안정화시켜주는..? 그런 용도라고. 오픈 소스에서 설치하는거에 문제가 있기 때문에 그런것이니 오픈소스에서 업데이트가 되어야 하고 당분간은 지금 진행했던 경로로 설치를 해야한다고 하셨다.

오픈 소스 이 사람들아... 빨리 업데이트 좀 해주세요.🙇🏻‍♀️🙇🏻🙇🏻‍♂️ 간곡히 부탁드립니다.

24년을 보내며 작성해보는 일 년 돌아보기 회고록.
이번년도 나에게 무슨 일이 있었는가.

1. 재작년 연말에 라섹을 해서 연초에는 눈을 회복하는데 힘썼다.
2. 계단에서 구르는 바람에 멍이 크게 들고 약한 뇌진탕을 진단 받아 진료를 꾸준히 받아야 했다.
3. 중학교 친구들과 베트남 나트랑 여행을 다녀왔다.
4. 마음의 병이 와서 정신건강의학과 진료 및 치료를 받았다.
5. 남자친구와 진지하게 결혼 이야기를 나눴고 미래를 약속했다.
6. 결혼은 하지 않을거라고 늘 단호하게 말하던 내가 처음으로 결혼하고 싶은 남자가 생겼다고 가족들에게 말했다.
7. 한 때는 친했지만 사이가 소원해져 마음이 멀어져버린 친구의 사과를 받아주었다.
8. 대학교 친한 친구가 결혼을 해서 다른 친구들과 축무를 했다.
9. 친한 친구의 아버지가 돌아가셔서 위로 해 주기 위해 바로 달려갔다.
10. 병원 치료를 잘 받고 상태가 호전되어 약을 끊었다.
11. 무슨 일을 하고싶은지 찾아나서 부트캠프에 지원, 수강중이다.
12. 부트캠프에서 좋은 사람들을 만난 일

아쉬웠던 점

  • 축무 조금 더 열심히 연습해서 더 완벽하게 했으면 좋았을 듯
  • 조금 더 조심히 주의를 기울여서 다치지 않게 나 자신을 잘 돌볼 걸
  • 눈치가 빠른거라 해야하나 타이밍이 좋은편이라고 해야하나 원치 않은 사실도 다 알아버리는 내가 고생을 조금 했어서.. 눈치 빠른게 아쉬웠다.

잘했던 점

  • 나에게 비는 용서들을 받아준 일(친구, 가족)
  • 친한친구 아버지의 장례식장에 첫번째로 인사 드린 일
  • 병원에 가기 꺼려졌지만 나를 아끼는 사람들을 생각하며 열심히 치료 받은 것

말도 많고 탈도 많고 문제도 많았던 24년.
심지어 연말엔 국정도 난리, 예상치 못한 참사로 힘들고 슬픈 사람들이 많은 해였던것 같다.

내가 좋아하는 문장중에

그럼에도 불구하고

라는 말이 있다. 국어사전에 의하면 "비록 사실은 그러하지만 그것과는 상관없이" 라는 의미이다.

내가 처한 현실은 그랬지만,
비록 24년은 그랬지만,

그럼에도 불구하고.
그랬던 과거와는 상관없이 살아남은 자들은 또 계속 앞으로 나아가야한다.
오늘을 살아낸 우리는 내일을 또 살아갈 것이기 때문에.

나 자신아, 24년도 더럽게 힘들었지. 그래도 잘 버텨주고 살아남아줘서 고마워 고생했어. 열심히 버텨줘서 25년도를 맞이할 수 있게 됐네. 새해 복 많이 받고 내년도 열심히 살아남자. 화이팅

리액트 강의를 들으며 평화롭게 설치를 하던 중 난데 없는 날벼락이 발생하고 말았다.

두둥-

아니 강사 양반 나는 설치가 안되는데 어찌하여 그대만 혼자 쭉쭉 나간단 말이오.🤷🏻‍♀️

알수 없는 오류만 잔뜩인 채 해결하기 위해 구글링을 시작했다.

a complete log of this run can be found in

라고 검색해보면 관련된 후기 및 해결 방법 블로그들이 잔뜩 나타났다.

그 중 대표적인 방법으로 우선 캐시를 지워주는 일.

npm cache clean --force

하지만 이 역시 리액트가 설치되진 않았다.

그 다음으로 해본 일은 명령어 수정,

npm init react-app .
이라고 작성했던 명령어를 대신해서

npx create-react-app .
이라고 작성해주었다.

하지만 이것도 역시 오류가 발생했고,,,

전역으로 설치해보라는 글도 있어서 전역으로 설치도 시도 했으나 마찬가지였다.

npm install -g create-react-app
create-react-app C:\Users\사용자명\Desktop\hello_react

혼자서는 도저히 답이 나오지 않는것 같아 머리를 부여잡고 강사님을 찾아갔다.🥲

오류가 나는 화면을 들고 찾아가 도움을 요청 드렸더니 흔쾌히 요청을 수락해주셨다. (강사님껜 악몽의 시작이셨을지도 모를..) 그렇게 줌으로 강사님께 화면 공유를 했고, 강사님께서 이것저것 확인해보시며 나의 상황에 대해서 파악 해주셨다.

1. create-react-app이 deprecated (더 이상 업데이트 없음) 되서 node 최신 버전이랑 안맞을 수 있음
→node 버전 더 낮은 거 설치
→ nvm 설치해서 node 버전 여러 개 설치 가능
→ 혹은 vite 로 설치해서 확인 

2. 알약 같은 보안 프로그램이 실행을 막음 → 알약 및 보안 프로그램 삭제 필요
→ npm create vite@latest . -- --template react

으로 축약되어 해결을 위해 같이 열심히 2-3시간 동안 붙잡고 있었다.

(고치는 과정 사진이 없는게 조금 아쉽긴 하다.)

줌 화면으로 화면 공유하며 강사님의 명렁어 입력기가 된 나와 강사님의 디스코드 대화창

하지만 환경변수를 들여다보고, node를 삭제했다 다시 깔아보고, nvm을 깔아봐도 도무지 해결되지 않는 나의 리액트,,🥲

강사님께선 왠지 경로에 한글이 들어가 있어서 깨지는 걸수도 있다고 하셨다.

C드라이브\Users\여기에 들어가는 사용자명을 내가 윈도우 설치할 때 한글 내 별명으로 해버렸다.

과거의 노트북 구매 당시의 나 자신아.. 왠만하면 앞으로 사용자명은 영어로 하자..(멘토님께서 대문자 상관 없긴 하지만 혹시모를 오류 방지를 위해 소문자로 쓰자고 당부하셨다.)

그렇게 주말 강사님과 함께 지지고 볶던 시간이 지나고,, 울적한 마음에 사설 디스코드 방에서 신세한탄을 하고 있었다.

그렇게 시작된 멘토님의 도전! 😂

갑자기 원격제어로 한번 봐주시겠다면서 원격제어를 걸어 상태를 살펴봐 주셨다. (이 때도 캡쳐를 못해서 아쉽긴 하다.)

환경 변수를 다 지워도 보고, 관리자 권한도 줘보고, 버전도 계속 체크해보고, VS코드의 bash와 powershell에서도 해보고, 경로에 한글이 들어가지 않게 C드라이브 바로 밑에 폴더를 만들어서도 해보고,, 하여튼 할 수 있는건 다 해본것같다. 장장 2시간 동안...

진짜 너무너무 고생해주신 멘토님,,🥲날 봐주신다고 돈이 되는것도 아닌데 자신의 일처럼 열정적으로 나서주셔서 감사했습니다.

그래서 이 때부턴 백업준비에 들어갔었다. 월-금은 평일 주강사님이 봐주시고, 토요일은 주말 주강사님이 봐주시는데 내가 문제가 발생했던게 금요일 저녁이라 토요일 낮에 주말 주강사님이랑 뚝딱뚝딱하고, 토요일 저녁에 멘토님이랑 뚝딱뚝딱했는데도 되지 않아서 이 땐 마음을 어느정도 해탈하고 노트북에 있는 자료들을 열심히 클라우드로 옮겼다.

그러다 파트 1 때 같은 팀이었던 다른 수강생분이 "저도 리액트 설치과정에서 오류가 났었는데 평일 주강사님과 해결했어요, 한번 찾아가보시는게 어떨까요?" 라는 의견에 화요일에 조심스럽게 평일 강사님께 DM을 드렸다.

어마무시한 리액트 설치 과정

역시나 오류를 찾아가는 과정중에 나는 한가하게 캡쳐 같은걸 하고 있을 수가 없으니, 강사님께서 보내주신 명령어들만 남아있다.

강사님께서 내게 보내주신 명령어들

자세한건 잘 모르겠지만 환경 변수에 설정되어 있는 경로들을 찾아가서 npm, nvm 폴더들도 깔끔히 삭제하고, 환경 변수에 지정 되어있던 %NVM_HOME%과 %NVM_SYMLINK%도 삭제하고,, 로컬 C 파일 탐색기에서 node와 nvm도 따로 검색해서 더 이상 뜨는게 없는지 점검하고, c드라이브 바로 아래에 있는 디렉토리에서 vs코드 열어 명령어 입력 뚝딱뚝딱 해줬더니 두둥..!

대박 성공해버렸어!!!!

와아..!! 백업의 마음준비를 하고 마지막 끈으로 찾아갔던 평일 강사님이 해결해주셨다.😂😂😂

상황이 어찌 됐던건지 어리둥절한 나를 위해 강사님께서 말씀해주시기를

1. 환경변수가 꼬임
2. 삭제가 제대로 안됨
3. 경로에 한글이 있어서

-앞으로 파워쉘로 해야한다.
-C드라이브에서 어플리케이션 수행할 것 (미션도 C드라이브 밑에다가 할 것!)

이라고 하셨다. 와아아..! 포맷 안해도 된다!😂

진짜 너무너무 감사했고, 죄송스러웠던 강사님들과 멘토님... 인사는 계속 드렸지만 그래도 회고록을 빌어 한번 더 인사를 전하고 싶습니다. 당신의 일들도 아닌데 열심히 봐주고 해결해주려고 노력해주셔서 정말정말 감사드립니다.🙇‍♀️❤️

리액트 공부... 열심히 해야겠다.🥲👍

+추가 (25.01.01)

결국엔 포맷엔딩을 탔다.

브라우저

웹 페이지, 이미지, 비디오 등의 콘텐츠를 수신, 전송 및 표현하는 소프트웨어이다. 인터넷에 접속하기 위해 사용하는 크롬, 사파리, 엣지, 파이어폭스 등이 브라우저이다.

웹 브라우저의 동작 원리는 여러 단계로 나뉘어져 있는데 사용자가 웹 페이지를 요청하고 이를 렌더링하는 과정에서 다양한 기술이 활용된다.

브라우저의 구조

  1. 사용자 인터페이스(User Interface) : 주소 표시줄, 각종 버튼(이전/다음, 새로고침, 설정, 닫기 등), 북마크 메뉴 등. 요청한 페이지를 보여주는 영역을 제외한 나머지 모든 부분이 사용자 인터페이스에 해당한다.
  2. 브라우저 엔진 : 사용자 인터페이스와 렌더링 엔진 사이의 동작을 제어해주는 엔진. 자료 저장소를 참조하며 로컬에 데이터를 읽기/쓰기 하면서 다양한 작업을 한다.
  3. 렌더링 엔진 : 요청한 콘텐츠를 표시한다. 요청한 웹 페이지의 HTML과 CSS를 파싱하여 화면에 표시
  4. 통신 : HTTP 요청과 같은 네트워크 호출에 사용
  5. 자바스크립트 해석기 : 자바스크립트 코드를 해석하고 실행
  6. UI 백엔드 : input, select 와 같이 기본적으로 지원하는 요소를 그린다. OS 사용자 인터페이스에서 정해준것들을 사용한다.
  7. 자료 저장소 : 쿠키, 로컬 스토리지와 같이 로컬 영역에 저장해야 하는 모든 종류의 데이터들을 저장한다.

1. URL 입력 및 요청

사용자가 웹 브라우저에 URL을 입력하면, 브라우저는 해당 URL을 해석하여 웹 서버에 요청을 보낸다. 이 과정에서 DNS(Domain Name System)를 통해 도메인 이름을 IP 주소로 변환한다.

2. 요청 전송

브라우저는 HTTP(HyperText Transfer Protocol) 또는 HTTPS(HyperText Transfer Protocol Secure) 프로토콜을 사용하여 웹 서버에 요청을 전송한다. 이 요청은 보통 GET 요청으로 이루어지며, 서버에게 특정 리소스(예: HTML 파일, 이미지 등)를 요청한다.

3. 서버 응답

웹 서버는 요청을 처리한 후, 요청된 리소스를 포함한 HTTP 응답을 브라우저에 반환한다. 이 응답에는 상태 코드(예: 200, 404)와 함께 HTML 문서, CSS 파일, JavaScript 파일 등이 포함될 수 있다.

4. HTML 파싱 및 DOM 생성

브라우저는 응답받은 HTML 문서를 파싱하여 DOM(Document Object Model) 트리를 생성한다. DOM은 HTML 문서의 구조를 나타내는 객체 모델이다.

5. CSS 파싱 및 CSSOM 생성

브라우저는 HTML 문서와 함께 요청한 CSS 파일을 파싱하여 CSSOM(CSS Object Model) 트리를 생성한다. CSSOM은 스타일 정보를 구조화하여 브라우저가 렌더링을 할 수 있도록 돕는다.

6. JavaScript 실행

브라우저는 HTML 문서 내의 JavaScript 코드를 실행한다. 이 과정에서 JavaScript는 DOM과 CSSOM을 수정할 수 있으며, 브라우저는 이를 반영하여 페이지를 업데이트한다.

7. 렌더 트리 생성

브라우저는 DOM 트리와 CSSOM 트리를 결합하여 렌더 트리를 생성한다. 렌더 트리는 화면에 표시될 요소들만 포함하며, 각 요소의 스타일과 위치 정보가 포함된다.

8. 레이아웃 및 페인팅

렌더 트리를 기반으로 브라우저는 레이아웃을 계산하여 각 요소의 정확한 위치와 크기를 결정한다. 이후, 이 정보를 바탕으로 화면에 각 요소를 그리는 과정을 페인팅이라고 한다.

9. 최적화 및 재렌더링

브라우저는 페이지가 동적으로 업데이트될 때 최적화를 통해 불필요한 렌더링을 줄인다. JavaScript가 DOM을 수정하면, 브라우저는 필요한 부분만 다시 렌더링한다.

결론

웹 브라우저는 이러한 여러 단계를 통해 사용자가 요청한 웹 페이지를 화면에 표시한다. 이 과정에서 다양한 프로토콜과 기술이 사용되며, 각 단계는 브라우저의 성능과 사용자 경험에 큰 영향을 미친다.

참고자료1

'코린이 개념잡기 > JavaScript' 카테고리의 다른 글

렉시컬 스코프(Lexical scope)  (3) 2025.01.03
자바스크립트 this  (0) 2025.01.03
var, let, const  (0) 2024.12.28
얕고(Shallow), 깊은(Deep) 복사(Copy)  (1) 2024.12.20
==와 ===의 차이  (0) 2024.12.20

💭 var, let, const 를 중복 선언 허용, 스코프, 호이스팅 관점에서 서로 비교해 보자.

중복 선언 허용

  var let, const
중복 선언 허용 O X
  • 한 스코프에서 같은 변수를 let이나 const로 두 번 선언하면 에러가 발생한다.
  • var는 변수의 중복 선언을 허용하는데 var로 같은 변수를 여러번 중복으로 선언할 수 있다.
  • 하지만 이미 선언된 변수에 var를 사용하면 두번째 선언문은 무시된다.
let user;
let user; // SyntaxError: 'user' has already been declared
var user = "Pete";
var user = "John"; // 이 "var"는 아무것도 하지 않는다(이전에 이미 선언됨).
// ...에러 또한 발생하지 않는다.

alert(user); // John

스코프

  var let, const
스코프 함수 스코프 블록 스코프
함수를 기준으로만 적용되는 스코프 {중괄호}로 감싸진 코드 블록을 기준으로 적용되는 스코프
var로 선언한 변수의 스코프는 함수 스코프이거나 전역 스코프이다.
블록 기준으로 스코프가 생기지 않기 때문에 블록 밖에서 접근이 가능하다.
{블록}을 감싸더라도 블록을 무시하기 때문에 블록 안에 작성해도 전역 변수가 된다. 블록문 안에서 let이나 const를 사용 했다면 블록문안에서만 접근할 수 있다.
코드 블록이 function 안에 있다면 var는 함수 레벨 변수가 된다.
function sayHi() {
  if (true) {
    var phrase = "Hello";
  }

  alert(phrase); // 제대로 출력
}

sayHi();
alert(phrase); // Error: phrase is not defined

호이스팅

  • let과 const로 선언한 변수는 선언되기 이전에 사용될 수 없다.
  • 하지만 var 변수는 함수 스코프를 기준으로 선언되기 이전에도 변수에 접근이 가능하다.
  • var 선언은 함수가 시작될 때 처리된다. 전역에서 선언한 변수라면 스크립트가 시작될 때 처리
  • 함수 본문 내에서 var로 선언한 변수는 선언 위치와 상관없이 함수 본문이 시작되는 지점에서 정의된다.
function sayHi() {
  phrase = "Hello";

  alert(phrase);

  var phrase;
}
sayHi();
function sayHi() {
  var phrase;

  phrase = "Hello";

  alert(phrase);
}
sayHi();

var phrase가 위로 이동한것처럼 적용되서 위의 두 코드는 동일하게 작동한다.

var로 선언한 변수 호이스팅 const, let로 선언한 변수 호이스팅
변수가 호이스팅 될 때는 선언, 초기화만 된채로 호이스팅 되고 할당까지 호이스팅 되지 않기 때문이다. const와 let은 var의 모호하고 too much로 유연한 문제점을 보완하기 위해 등장한 개념인 만큼 const와 let으로 변수가 선언되기 이전 라인에 해당 변수를 출력하는 코드를 작성한 경우 참조 오류가 발생한다.
하지만 오류가 발생했다고 해서 const와 let으로 선언한 변수는 호이스팅의 예외가 되는 것은 아니다.

여기서 TDZ(Temporal Dead Zone)에 대한 개념이 나온다.

'코린이 개념잡기 > JavaScript' 카테고리의 다른 글

자바스크립트 this  (0) 2025.01.03
웹 브라우저의 동작 원리  (1) 2024.12.28
얕고(Shallow), 깊은(Deep) 복사(Copy)  (1) 2024.12.20
==와 ===의 차이  (0) 2024.12.20
자바스크립트 모듈  (0) 2024.12.19

axios 문법

axios는 fetch보다 문법이 조금 더 간단하고 다양한 편의기능을 제공하기 때문에 실무에서 많이 사용한다.

axios는 서드파티 모듈이기 때문에 설치를 해줘야 하는데 터미널을 키고 npm install axios 라고 입력해주면 된다.

axios 설치

axios는 HTTP 메소드 이름과 동일한 메소드를 사용하고 리스폰스 바디를 data 프로퍼티로 접근할 수 있다.

GET 리퀘스트

axios로 GET 리퀘스트를 보내려면 axios.get 메소드를 사용하면 된다.

// axios
import axios from 'axios'; //axios import하기

async function getColorSurvey(id) {
  const res = await axios.get(`https://learn.codeit.kr/api/color-surveys/${id}`);
  return res.data;
}

axios 역시 promise를 리턴하기 때문에 앞에 await을 적어줘야 한다. 그리고 axios에서는 바디 내용을 파싱할 필요 없이 그냥 data 프로퍼티로 가져오면 된다. 상태 코드 status나 헤더 headers를 가져올 수도 있다. 그리고 그걸 데이터 변수에 할당할 필요 없이 그냥 리턴해줄 수 있다.

// fetch

async function getColorSurvey(id) {
  const res = await fetch(`https://learn.codeit.kr/api/color-surveys/${id}`);
  const data = await res.json();
  return data;
}

쿼리 파라미터도 조금더 쉽게 처리할 수 있는데 쿼리파라미터를 보낼 경우 url 객체를 사용할 필요없이 params 옵션을 사용하면 된다.

// axios

export async function getColorSurveys(params = {}) {
  const res = await axios.get(
    'https://learn.codeit.kr/api/color-surveys', 
    { params }, //이 문법은 params라는 프로퍼티로 params 객체를 설정하는 것이다.
  );
  return res.data;
}

이렇게 쿼리 파라미터를 담고 있는 객체를 전달하면 객체에 있는 프로퍼티들로 알아서 쿼리 스트링을 만들고 URL 뒤에 붙여서 리퀘스트를 보내준다. 만약 프로퍼티 값이 null이나 undefined이면 그 프로퍼티를 무시하고 쿼리 스트링을 만들어준다.

// fetch

async function getColorSurveys(params = {}) {
  const url = new URL('https://learn.codeit.kr/api/color-surveys');
  Object.keys(params).forEach((key) =>
    url.searchParams.append(key, params[key])
  );
  const res = await fetch(url);
  const data = await res.json();
  return data;
}

params 외에 헤더 같은 정보도 옵션으로 설정할 수 있다. 

POST 리퀘스트

POST는 axios.post를 사용하면되고, 각 HTTP 메소드는 동일한 이름의 axios 메소드를 쓰면 된다. 그리고 POST 메소드는 바디로 전달할 데이터를 두번째 아규먼트로 받는데 자바스크립트 객체를 그대로 사용할 수 있다.

// axios

async function createColorSurvey(surveyData) {
  const res = await axios.post('https://learn.codeit.kr/api/color-surveys', surveyData);
  return res.data;
}

axios가 알아서 자바스크립트 객체를 JSON 문자열로 변환해 주기 때문에 JSON.stringify 같은 메소드를 쓰지 않아도 된다. 그리고 헤더도 바디 데이터를 보고 알아서 설정해 주기 때문에 내가 설정하지 않아도 된다.

// fetch

async function createColorSurvey(surveyData) {
  const res = await fetch('https://learn.codeit.kr/api/color-surveys', {
    method: 'POST',
    body: JSON.stringify(surveyData),
    headers: {
      'Content-Type': 'application/json',
    },
  });

  if (!res.ok) {
    throw new Error('데이터를 생성하는데 실패했습니다.');
  }

  const data = await res.json();
  return data;
}
  • 리퀘스트에 바디가 필요 없는 get이나 delete는 옵션을 두 번째 파라미터로 받는다.
  • 바디가 필요한 post, patch, put은 바디 데이터를 두 번째 아규먼트로 받고,
  • 옵션을 세 번째 아규먼트로 받는다. 옵션에서는 헤더, 쿼리 파라미터, 타임아웃 등 다양한 설정을 할 수 있다. <참고>

리퀘스트에 바디가 필요 없는 get이나 delete는 옵션을 두 번째 파라미터로 받는다
바디가 필요한 post, patch, put은 바디 데이터를 두 번째 아규먼트로 받고

axios 인스턴스

리퀘스트마다 공통되는 부분이 있으면 인스턴스를 생성하고 인스턴스로 리퀘스트를 보내면 된다. 예를들어 리퀘스트 URL은 보통 경로만 다르고 앞부분은 항상 동일하다. base URL로 앞부분을 설정하고 리퀘스트 timeout을 3초로 설정해보자.

const instance = axios.create({
  baseURL: 'https://learn.codeit.kr/api', //baseURL에서 URL은 모두 대문자로 작성하자
  timeout: 3000,
}); //이제 인스턴스에서 baseURL을 설정했으니  axios를 사용하는 부분들을 바꿔주면 된다.

async function getColorSurveys(params = {}) {
  const res = await instance.get(`/color-surveys`, { //나머지 함수들도
    params, //비슷하게 바꿔주면 된다.
  });
  return res.data;
}

인스턴스로 리퀘스트를 보내고 있기 때문에 모두 baseURL과 timeout이 설정되어 있다. 이제 URL 앞부분이 바뀌거나 해도 한군데서만 바꾸면 된다. 인스턴스를 만들 때 설정할 수 있는 옵션은 리퀘스트를 보낼 때 설정할 수 있는 옵션과 똑같다.

axios 오류 처리

fetch 함수는 리퀘스트 자체가 실패했을 때만 promise가 reject되고 400이나 500 에러 리스폰스가 돌아왔을 때는 promise가 reject 되지 않았다.

axios는 리퀘스트 자체가 실패하거나 리스폰스의 상태 코드가 실패(4XX, 5XX)를 나타내면 Promise를 reject 한다. 그러니까 리퀘스트가 성공하고 200대의 상태코드를 가진 리스폰스가 돌아와야만 promise가 fulfilled가 되는 것이다. (그래서 오류처리가 좀 더 편하다.) axios 함수들을 호출할 때 try catch로 감싸주면 된다.

//main.js
import { getColorSurveys, getColorSurvey, createColorSurvey } from './axiosApi.js';

try {
  const survey = await getColorSurvey(123);
  console.log(survey);
} catch (e) {
  console.log('오류가 발생했습니다.');
  console.log(e.message);
}

에러 객체의 메세지는 axios가 알아서 설정해 주지만 이거 대신 에러 리스폰스 바디에 있는 메시지를 사용할 수도 있다. 에러 리스폰스가 돌아오면 axios는 에러 객체의 리스폰스를 저장해준다. 

axios가 알아서 정해준 에러 객체 메세지

//main.js
import { getColorSurveys, getColorSurvey, createColorSurvey } from './axiosApi.js';

try {
  const survey = await getColorSurvey(123);
  console.log(survey);
} catch (e) {
  console.log('오류가 발생했습니다.');
  console.log(e.response); //response 프로퍼티로 변경
}

이렇게 response 프로퍼티로 접근할 수 있는데 이 response도 상태코드 status, 헤더 headers, 바디 데이터 data 프로퍼티가 있다.

//main.js
try {
  const survey = await getColorSurvey(123);
  console.log(survey);
} catch (e) {
  console.log('오류가 발생했습니다.');
  console.log(e.response.status); //상태코드
  console.log(e.response.data); //바디내용
}

프로퍼티를 더 붙여주면 에러 리스폰스의 상태코드와 바디내용 등 출력하고자 하는 내용만 따로 출력할 수도 있다.

상태코드 404, 바디내용 NF

//api.js
export async function createColorSurvey(surveyData) {
  const res = await axios.post('https://learn.codeit.kr/api/color-surveys', surveyData);
  return res.data;
}
//main.js
import { createColorSurvey } from './api.js';

const surveyData = {
  mbti: 'EEEE',
  colorCode: '#CDCDCD',
  password: '0000',
};

try {
  const newColorSurvey = await createColorSurvey(surveyData);
  console.log(newColorSurvey);
} catch (e) {
  if (e.response) { 
    // 리퀘스트는 성공했지만 상태 코드가 실패(4XX, 5XX)를 나타냄
    console.log(e.response.status);
    console.log(e.response.data);
  } else { 
    // 리퀘스트 자체가 실패
    console.log('리퀘스트가 실패했습니다.');
  }
}
Invalid MBTI //잘못된 MBTI

한 가지 주의해야할 점은 리스폰스가 돌아왔을 때만 리스폰스 객체가 존재한다는 것이다. status나 data같은 프로퍼티를 접근할 거라면 리스폰스 객체의 존재여부를 먼저 확인해야한다. 

네트워크 리퀘스트를 보내는 코드는 보통 웹 브라우저에서 실행된다. 웹 브라우저에서는 개발자 도구로 네트워크 액티비티를 확인 할 수 있다. (이번 포스팅의 목적 : 개발자 도구의 네트워크 기능 알아보기)

웹 개발을 할 때는 네트워크 리퀘스트를 보내는 부분에서 생각지 못한 버그가 발생할 수 있는데 이럴 때 개발자 도구가 굉장히 유용하게 쓰인다. 

<!-- index.html -->
<!DOCTYPE html>
<html lang="ko">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>네트워크 요청 데모</title>
        <style>
            body {
                font-family: Arial, Helvetica, sans-serif;
                margin: 16px;
            }
            div {
                margin: 8px 0;
            }
            span {
                margin-right: 8px;
            }
        </style>
    </head>
    <body>
        <h2>네트워크 요청 데모</h2>
        <input id="mbti" placeholder="MBTI" />
        <input id="colorCode" placeholder="#000000" />
        <button>리퀘스트 보내기</button>
        <div class="status"></div>
        <div class="data"></div>
        <script type="module" src="demo.js"></script>
    </body>
</html>
//demo.js
import { getColorSurvey, createColorSurvey } from './api.js';

const btn = document.querySelector('button');
const statusDiv = document.querySelector('div.status');
const dataDiv = document.querySelector('div.data');
const mbtiInput = document.querySelector('#mbti');
const colorCodeInput = document.querySelector('#colorCode');

btn.addEventListener('click', async function (e) {
    statusDiv.textContent = '로딩 중...'; //이 함수는 우선 status를 로딩중으로 설정해주고
    dataDiv.innerHTML = '';
    try {
        const survey = await getColorSurvey(3); //이렇게 리퀘스트를 보낸다. getColorSurvey함수를 실행하고 있는데
        statusDiv.textContent = '완료'; //리스폰스가 돌아오면 status를 완료로 바꾸고
        dataDiv.innerHTML = `<span>${survey.mbti}</span><span>${survey.colorCode}</span>`; //dataDiv 안에 설문 객체의 mbti 필드와 colorCode 필드를 보여준다.
    } catch (e) { //만약 리퀘스트를 보내는 부분에서 에러가 나면
        statusDiv.textContent = '오류'; //status를 오류로 바꾸고 
        dataDiv.innerHTML = `<span>${e.message}</span>`; //에러 메세지를 dataDiv 안에서 보여준다.
    }
});

위 코드 그대로 실행해본 화면

페이지에는 그냥 폼 요소랑 버튼, 그리고 텍스트가 있다. html에서 body태그 안에 class status는 네트워크 요청 상태를 보여줄 div이고 class data div는 네트워크 요청 결과를 보여줄 div이다. 그리고 html 파일은 demo.js 파일과 연결되어 있는데 demo.js 파일을 보면 일단 API 함수들을 임포트하고 페이지에 있는 각종 요소들을 가져오고 있다. 그리고 중요한 이벤트 영역이 있는데 버튼을 클릭하면 안에 있는 함수(async function (e)부터 dataDiv.innerHTML = `<span>${e.message}</sapn>`;}});까지)가 실행된다.

이벤트의 상세 내용에 대해선 위에 있는 demo.js의 코멘트를 확인해보자.

개발자 도구의 Network 탭을 들어가보면 보이는 화면

개발자 도구가 열려 있는 상태라면 네트워크 요청이 기록되는데 새로고침을 해보면 어떤 기록들이 생성된다.

스티커가 위치한 부분이 기록들이다.

이 기록들 하나하나가 다 리퀘스트이다. 각 리퀘스트의 상태 코드(status), 타입(type), 어디서 리퀘스트를 보냈는지(Initiator), 사이즈(size), 걸린 시간(time) 등을 볼 수 있고, 마지막 waterfall은 리소스를 가져오는 과정과 타이밍을 그림으로 그린 것이다. 200ms 400ms 600ms 등 써 있는 중간 부분은 전체적인 waterfall을 보여주는 것이다.

type이 fetch라고 쓰여있는 리퀘스트를 선택해보면 waterfall이 조금씩 바뀌는것을 확인할 수 있다. 

그리고 컬럼 헤더에 마우스를 올려 우클릭을 해보면 다른 정보도 볼 수 있다. 리퀘스트 Method도 볼 수 있게 Method를 선택해보자.

메소드를 선택하면 메소드 칸이 추가된다.

이제 리퀘스트를 선택해보면 여러 탭을 클릭할 수 있다. 

Headers에서는 리퀘스트와 리스폰스의 헤더를 확인 할 수 있다.

그리고 Response는 리스폰스의 바디를 확인할 수 있는데, 

아래쪽에 라이언 스티커 옆에 있는 중괄호 아이콘

중괄호 아이콘을 클릭하면 압축된 결과와 예쁘게 출력된 결과를 확인할 수 있다.

그리고 Preview 탭은 리스폰스 내용의 미리보기를 보여준다. 지금처럼 JSON의 경우 큰 차이가 없지만 리스폰스 내용이 HTML인 경우 HTML에 대한 화면을 보여준다.

HTML에 대한 화면을 보여준다.

디버깅(debugging 컴퓨터 프로그램 개발 단계에서 발생하는 오류나 비정상적인 연산(버그)을 찾아내고 수정하는 작업 과정)을 하다보면 헤더가 제대로 설정되고 있는지, 예상된 리스폰스가 돌아오고 있는지 등을 확인해야 하는 경우가 많은데 그럴 때 Network 탭으로 리퀘스트, 리스폰스의 모든 정보를 볼 수 있기 때문에 아주 유용하다.

import { getColorSurvey, createColorSurvey } from './api.js';

const btn = document.querySelector('button');
const statusDiv = document.querySelector('div.status');
const dataDiv = document.querySelector('div.data');
const mbtiInput = document.querySelector('#mbti');
const colorCodeInput = document.querySelector('#colorCode');

btn.addEventListener('click', async function (e) {
    statusDiv.textContent = '로딩 중...';
    dataDiv.innerHTML = '';
    try { //POST 리퀘스트 보내기
        const surveyData = {
            mbti: mbtiInput.value, //Input 필드의 값을 받아서 surveyData 객체를 만들고
            colorCode: colorCodeInput.value, 
            password: '0000',
        };
        const survey = await createColorSurvey(surveyData); //createColorSurvey 함수의 아규먼트로 surveyData를 전달 
        statusDiv.textContent = '완료';
        dataDiv.innerHTML = `<span>${survey.mbti}</span><span>${survey.colorCode}</span>`;
    } catch (e) {
        statusDiv.textContent = '오류';
        dataDiv.innerHTML = `<span>${e.message}</span>`;
    }
});

이렇게 코드를 작성해보면, 개발자 창으로 가봤을 때 새로고침이 되면서 과거 기록이 삭제되어 있을것이다. 만약 기록을 지우지 않고 남기고 싶다면 preserve log를 선택하면 된다.

폼에 잘못된 mbti 정보를 입력하고 리퀘스트를 보내보자. 

preflight 라는 리퀘스트는 브라우저가 자동으로 보내는 건데 지금은 무시해도된다. 오류가 발생한 POST 리퀘스트를 클릭해보면 400 상태 코드가 돌아온걸 확인할 수 있고, payload 탭에서는 리퀘스트 바디를 확인할 수 있다. 리스폰스 바디는 그냥 문자열이다.

그럼 제대로 된 mbti를 입력하고 다시 리퀘스트를 보내보면 어떨까?

이번엔 리퀘스트가 성공적으로 처리됐다.
성공한 리퀘스트의 헤더, 페이로드, 리스폰스

여기에 자주 사용하는 기능 두가지가 더 있는데 

Filter 인풋창 옆으로 쭉 있는 항목들이 필터이다.

위에 있는 Filter를 이용해서 원하는 리퀘스트 타입만 볼 수가 있다. 예를 들어 fetch를 클릭하면 

이렇게 fetch 리퀘스트만 모아서 볼 수 있다. 원하는 리퀘스트를 찾을 때 아주 유용하다. 그리고 그 위에 있는 throttling 기능도 많이 사용하는데

이걸 클릭하면 3G처럼 느린 인터넷 속도, 아니면 아예 오프라인 상태를 흉내낼 수도 있다. 지금은 리퀘스트가 하도 빨리 처리되서 중간에 로딩되는 상태를 보기가 어려운데 여기서 slow 3G를 선택하면 

로딩 중...

중간에 로딩이 되는 상태를 확실하게 확인해 볼 수 있다.

<네트워크 기능 참조>

네트워크 요청을 할 땐 다양한 오류가 발생할 수 있다. 

// api.js
export async function getColorSurveys(params = {}) {
  const url = new URL('https://learn.codeit.krrrrr/api/color-surveys'); //일부러 오류만들기
  object.keys(params).forEach((key) =>
    url.searchParams.append(key, params[key])
  );
  
  const res = await fetch(url);
  const data = await res.json();

  return data;
}

export async function getColorSurvey(id) {
  const res = await fetch(`https://learn.codeit.kr/api/color-surveys/${id}`);
  const data = await res.json();
  return data;
}

export async function createColorSurvey(sueveyData) {
  const res = await fetch('https://learn.codeit.kr/api/color-surveys', {
    method: 'POST',
    body: JSON.stringify(surveyData),
    headers: {
      'Content-Type': 'application/json'
    }
  });

  const data = await res.json();
  return data;
}

일부러 오류를 발생하게 오타를 만든뒤 main.js 파일을 실행해보면 fetch가 실패했다고 나온다. 

fetch failed
결과 화면

주소를 찾을 수 없기 때문에 오류가 발생하고 fetch가 리턴하는 promise가 rejected 상태가 된다. 이 오류는 이제 너무나도 익숙한 try catch문을 이용해 처리할 수 있다.

드래그 한 부분

왼쪽의 드래그 한 부분을 try catch로 감싸거나 함수를 실제로 호출하는 부분을 try catch로 감싸줘도 된다. try문 안에서 throw되는 오류는 모두 잡아주기 때문에 상관이 없다. 

//main.js
import { getColorSurveys, getColorSurvey, createColorSurvey } from './api.js';

try {
  const data = await getColorSurvey(); 
  console.log(data);
} catch (e) {
  console.log('오류가 발생했습니다:');
  console.log(e.message);
}

함수를 호출하는 부분을 try catch문으로 감싸주고 코드를 다시 실행해보면 

이번엔 그냥 fetch failed라고 나온다. try문이 안에서 발생한 오류르 잡아서 catch문 안에 있는 코드가 실행된 것이다. 지금은 콘솔에 오류 메세지를 출력하지만 실제 웹사이트 코드라면 오류 메세지를 화면에 보여줄 수 있을 것이다. 

유효하지 않은 주소를 입력하는 것 외에도 유효하지 않은 헤더 이름을 사용하거나 헤더 값이 이상하면 리퀘스트 자체가 실패해서 fetch가 리턴하는 promise는 rejected 상태가 된다. 주의해야할 점은 리퀘스트 자체가 실패했을 때만 promise가 rejected 상태가 되고 400이나 500같은 에러 리스폰스가 돌아오는 경우에는 promise는 fulfilled 상태가 된다.

그러니까 예를 들어 잘못된 바디 내용을 보내서 400리스폰스가 돌아오거나 서버 측에서 오류가 발생해서 500 리스폰스가 돌아와도 fetch의  promise는 fulfilled 상태가 된다. 그래서 이걸 깔끔하게 처리하는 방법은 리스폰스의 상태 코드가 성공을 나타내지않으면 오류를 발생시키는 것이다. 

// api.js
export async function getColorSurveys(params = {}) {
  const url = new URL('https://learn.codeit.krrrrr/api/color-surveys'); //일부러 오류만들기
  object.keys(params).forEach((key) =>
    url.searchParams.append(key, params[key])
  );
  
  const res = await fetch(url);
  //오류 발생
  if (!res.ok) {
    throw new Error('데이터를 불러오는데 실패했습니다.');
  }
  
  const data = await res.json();
  return data;
}

export async function getColorSurvey(id) {
  const res = await fetch(`https://learn.codeit.kr/api/color-surveys/${id}`);
  const data = await res.json();
  return data;
}

export async function createColorSurvey(sueveyData) {
  const res = await fetch('https://learn.codeit.kr/api/color-surveys', {
    method: 'POST',
    body: JSON.stringify(surveyData),
    headers: {
      'Content-Type': 'application/json'
    }
  });

  const data = await res.json();
  return data;
}

여기서 추가한 res.ok는 리스폰스 상태 코드가 2로 시작하면 true, 그렇지 않으면 false를 리턴한다. if문 안에서 리스폰스의 실제 상태 코드나 바디내용을 보고 더 세밀한 처리를 해줄 수도 있다. 리퀘스트 자체가 실패해도 오류를 throw하고 오류를 나타내는 상태 코드가 돌아와도 오류를 throw 하는 것이다. 

이렇게 처리를 하는게 헷갈리지 않고 함수를 사용할 때 더 편리하다.

일부러 만들었던 주소 오류는 수정을 하고, 유효하지 않은 mbti를 쿼리 파라미터로 보내본다면, 

//main.js
import { getColorSurveys, getColorSurvey, createColorSurvey } from './api.js';

try {
  const data = await getColorSurvey({ mbti: 'EEEE' }); 
  console.log(data);
} catch (e) {
  console.log('오류가 발생했습니다:');
  console.log(e.message);
}

방금 작성한 메세지가 잘 출력되는걸 확인 할 수 있다. 만약 if (!res.ok) {throw new Error('데이터를 불러오는데 실패했습니다.');} 이 코드가 없었다면 잘못된 mbti를 전달했기 때문에 상태코드 400이 돌아올것이다. 상태코드 400이 돌아와도 아무 문제없이 다음줄이 실행되는데 이 때 상태코드 400에 해당하는 리스폰스의 형식도 JSON이라면 바디가 파싱되서 그대로 리턴된다. 함수 안에서 아예 오류가 발생하지 않는 것이다. 반면 리스폰스 바디 형식이 JSON이 아니라면 오히려 바디를 파싱하는 과정에서 오류가 발생한다. 

이렇게 다양한 케이스가 있어서 헷갈릴 수 있기 때문에 리스폰스가 성공적으로 처리되지 않으면 그냥 오류를 throw 하는 것이다. 나머지 코드도 비슷하게 바꿔주면 이렇게 할 수 있다.

// api.js
export async function getColorSurveys(params = {}) {
  const url = new URL('https://learn.codeit.kr/api/color-surveys');
  object.keys(params).forEach((key) =>
    url.searchParams.append(key, params[key])
  );
  
  const res = await fetch(url);
  //오류 발생
  if (!res.ok) {
    throw new Error('데이터를 불러오는데 실패했습니다.');
  }
  
  const data = await res.json();
  return data;
}

export async function getColorSurvey(id) {
  const res = await fetch(`https://learn.codeit.kr/api/color-surveys/${id}`);
  
  if (!res.ok) {
    throw new Error('데이터를 불러오는데 실패했습니다.');
  }
  
  const data = await res.json();
  return data;
}

export async function createColorSurvey(sueveyData) {
  const res = await fetch('https://learn.codeit.kr/api/color-surveys', {
    method: 'POST',
    body: JSON.stringify(surveyData),
    headers: {
      'Content-Type': 'application/json'
    }
  });
  
  if (!res.ok) {
    throw new Error('데이터를 생성하는데 실패했습니다.');
  }

  const data = await res.json();
  return data;
}

마무리 복습하기

fetch 오류는 크게 두가지가 있다. 

  1. URL이 이상하거나 헤더 정보가 이상해서 리퀘스트 자체가 실패하는 경우
  2. 리퀘스트는 성공적인데 상태코드가 실패를 나타내는 경우

여기서 조심해야 할 부분은 첫번째 경우에만 fetch의 promise가 rejected 상태가 된다는 것이다. 그래서 오류를 깔끔하게 처리하는 방법 중 하나는 두번째 경우에도 수동적으로 오류를 throw 해주는 것이다. 리퀘스트가 성공적으로 처리되고, 성공적인 리스폰스가 돌아왔을 때만 데이터를 리턴하고 나머지 모든 경우엔 오류를 발생시키는 것이다. 

그래야 API 함수를 사용하는 입장에서 try catch로 쉽게 로직을 구분할 수 있다.

fetch에 대한 오류를 처리할 때는 언제 promise가 rejected 되는지, 어떤 내용이 리스폰스 바디로 돌아오는지를 잘 생각해보도록 하자.

보통 웹 개발을 할 때는 aPI를 호출하는 함수들을 따로 모아두고 필요할 때 import해서 사용한다.

//api.js
//GET/api/color-surveys를 하는 함수부터 만들어보자.
export async function getColorSurveys() {
  const url = new URL('https://learn.codeit.kr/api/color-surveys');
  url.searchParams.append('offset', 10);
  url.searchParams.append('limit', 10);
  
  const res = await fetch(url);
  const data = await res.json();

  return data;
}

함수 안에서 await을 사용하니 async 키워드도 추가해주고, 데이터를 리턴하는것으로 바꿔주었다. 그리고 위 코드에선 쿼리 파라미터가 고정되어 있는데 쿼리파라미터를 함수 파라미터로 대신 받도록 바꿔준다.

//api.js
export async function getColorSurveys(params = {}) {
  const url = new URL('https://learn.codeit.kr/api/color-surveys');
  object.keys(params).forEach((key) =>
    url.searchParams.append(key, params[key])
  );
  
  const res = await fetch(url);
  const data = await res.json();

  return data;
}

params 객체의 프로퍼티를 돌면서 url.searchParams에 추가해 주는 코드이다. 예를 들어 params에 이런 객체( params = { offset: 5, limit: 10}; )가 전달된다면 url.searchParams에 offset은 5, limit은 10이 추가된다.

이제 이 함수를 import해서 사용하면 되는데 main.js에서 나머지 코드 부분을 코멘트 처리하고 코드를 작성해준다.

//main.js
import { getColorSurveys } from './api.js';

const data = await getColorSurveys(); //async 함수는 promise를 리턴하기 때문에 앞에 await을 적어준다.
console.log(data);

이 상태로 실행해봐도 이전과 같이 리스폰스가 잘 출력된다.  여기에서 쿼리 파라미터도 바로 사용할 수 있는데

//main.js
import { getColorSurveys } from './api.js';

const data = await getColorSurveys({ offset:20, limit:20 });
console.log(data);

결과값

이번엔 offset과 limit에 따라 next와 previous의 값이 돌아오고, 결과도 총 20개가 돌아온다.

// api.js
// GET/api/color-surveys를 하는 함수
export async function getColorSurveys(params = {}) {
  const url = new URL('https://learn.codeit.kr/api/color-surveys');
  object.keys(params).forEach((key) =>
    url.searchParams.append(key, params[key])
  );
  
  const res = await fetch(url);
  const data = await res.json();

  return data;
}

// GET/api/color-surveys/:id
export async function getColorSurvey(id) {
  const res = await fetch(`https://learn.codeit.kr/api/color-surveys/${id}`);
  const data = await res.json();
  return data;
}

// POST/api/color-surveys
export async function createColorSurvey(sueveyData) {
  const res = await fetch('https://learn.codeit.kr/api/color-surveys', {
    method: 'POST',
    body: JSON.stringify(surveyData),
    headers: {
      'Content-Type': 'application/json'
    }
  });

  const data = await res.json();
  return data;
}

main.js에 적혀있던 코멘트한 리퀘스트 함수들을 api.js 파일로 긁어와서 구분, 작성해준 뒤 실행을 시켜보도록 하자.

//main.js
import { getColorSurveys, getColorSurvey, createColorSurvey } from './api.js'; //함수들을 import하고

const data = await getColorSurvey(10); //getColorSurvey에 id 10을 전달
console.log(data);

테스트용으로 getColorSurvey에 id 10을 전달해보았다. 

id: 10

id 10에 해당하는 설문 객체가 잘 돌아온다. 

//main.js
import { getColorSurveys, getColorSurvey, createColorSurvey } from './api.js';

// 새로운 설문 객체를 만들어보자.
const surveyData = {
  mbti: 'ENFJ',
  colorCode: '#ABCD00',
  password: '0000'
};

const data = await createColorSurvey(surveyData); 
console.log(data);

설문 객체 생성 완료!

설문 객체도 잘 생성됐다. 

참여 동기 및 과정 소개

프론트엔드는 아니고 웹 퍼블리셔 국비과정을 수료했던 경험이 있다. 디자인 위주의 수업이었고, 취업을 목표로 하는 학원 같지는 않아서 아쉬운 점도 있었다. (수강생 중에 60대 할아버지 두 분이 계셨는데, 수업 시간에 신문을 읽거나 딴짓을 해도 제지하는 게 없어서 살짝 당황스럽기도 했다.) 그래도 디자인과 코딩 수업을 통해 나는 디자인보다 코딩 수업을 더 좋아한다는 점과 프론트엔드 개발자라는 직업의 매력을 알게 되었다. 아무래도 눈으로 보이는 작업물을 다루다 보니 성과가 보여서 더 뿌듯하고 좋았고, 덕분에 부트캠프에 도전하는 계기가 되었다.

첫 시작

첫 시작 땐 팀원들과의 조우가 많이 긴장됐었다. 낯을 많이 가리는 편은 아니지만 새로운 시작은 긴장 될 수밖에 없는것 같다. 우리팀은 6명으로 구성되어 있었다. (다른 팀은 5명씩도 있었던 듯)

팀 활동

월요일부터 토요일까지 매일 팀 미팅을 진행했다. 첫 시간에 팀장 선정과 멘토링 일정 협의, 미팅 시간을 정하고 간단하게 자기소개를 했다. 

더보기

우리팀은 

월요일부터 목요일은 16시 미팅, 금요일은 20시 미팅, 토요일은 13시에 미팅을 진행했고, 멘토링은 월, 목 17시에 진행했다.

그리고 데일리 팀미션이라고, 팀미팅 시간 때 매일 돌아가며 배운 내용을 물어보는 활동과 더불어 한 명씩 돌아가며 배운 내용 중 주제를 정해 문제를 내면 팀원들이 구두로 대답하는 활동을 했는데 추후에 있을 취업 대비 면접을 준비하는 느낌으로 진행했다.

데일리 팀 미팅 6번 중 2번은 멘토링 활동도 같이 하는데, 현직 프론트엔드 개발자 멘토님과 함께하는 시간이었다. 팀원들과 협의해서 시간을 정하고 디스코드에서 만났는데 팀원들이 서로 잘 맞아서 팀 미팅 및 멘토링이 끝나고도 1시간 동안 잡담을 나눈 날도 있었다. (마지막 멘토링 때는 멘토링시간이 끝나고 난 뒤에도 헤어지기 아쉬워🥲 추가로 1시간동안 잡담을 나눴다.)

매주 토요일에는 위클리 페이퍼라는 활동도 했다. 코드잇에서 월요일에 새로운 주제를 디스코드에 공지해주면 그 주 토요일 전까지 해당 주제에 대해 생각을 정리해 팀원들과 토요일에 해당 주제에 대해서 이야기를 나누는 시간을 가졌다.

배운내용

파트1에서 HTML, CSS, Git, 유닉스 커맨드, 반응형 웹 퍼블리싱, 자바스크립트 중급, 인터렉티브 자바스크립트, 모던 자바스크립트, 비동기, 리퀘스트 등을 배웠다. (와, 이렇게 나열하니 정말 많다.)

그중에서 가장 기억에 남는 건 Git 수업이었다. 이전에 학원에서 잠깐 해본 기억이 있는데, 너무 어려워서 포기할까 고민도 했었던 애증의 Git. 하지만 Git은 필수라서 꼭 이해하고 싶었고, 수업에서 흥미를 갖고 공부하려고 노력했다.

느낀 점

강의는 온라인 강의 기반으로, 기초적인 이론 설명을 많이 다뤘는데, 조금 더 심화된 내용이라던지 이해를 위해 다른 내용을 추가로 알아야 하는 것은 직접 찾아보며 공부해야 했다. 그래서 초심자인 나에겐 조금 어렵게 느껴졌다. 어려울 땐 확실히 힘들긴 하다. "왜 이해를 이정도밖에 못할까?" 하고 자책스러운 마음도 있고, 다른사람들은 다 잘 나가고있는데 나만 이러는게 아닐까 하는 걱정도 들기도 했다. 하지만 나같은 비전공자에 초심자인 사람들도 분명 있었고, 그들과 얘기하면서 많이 위로를 얻었다. 이기적인것 같지만 나만 힘든게 아니라는 위로는 나를 괜찮게 많들기도 하는것 같다.

어렵긴 했지만 심화된 내용을 알아가기 위해 스스로 공부하는 방법도 깨닫기도 했고, 힘들 때는 토요일 보충 수업도 듣고(지금 껏 매 보충수업 올 출석인건 안비밀😅), 구글링으로 추가 학습도 하면서 계속 발전해 나가려고 노력했다.

우리 팀은 전공자도 꽤 있었는데 그들이 해주는 말들이 나에게 도움이 되기도 했다. 
유닉스 커맨드 파트를 정리하면서 듣느라 진도를 나가는게 좀 더디기도 했고, 어려워서 힘들어 했는데 '술술 머리에 스치듯이 정보를 입력하라'는 조언도 있었고, '그래도 중요한 부분이긴 하니 정리하면서 듣는게 나중에 훨씬 도움이 많이 될거다' 라는 의견도 들었다. 그런 얘기를 듣고나니 진도를 더디게 나가긴 했지만 머리에 조금이라도 남기려는 노력을 한 내가 조금 뿌듯해졌다.

그 외에도 팀원들이 추천하는 인프런 강의, 추천 교재(모던 자바 Deep Dive 등), 요약본 파일 공유(그저 빛✨) 등 말 뿐만 아니라 현실적인 도움도 많은 힘이 되었다.

앞으로의 계획

파트 2에 대한 기대와 걱정이 반반 섞여있다. 파트1 팀원들과 정말 잘 맞아서, 이렇게 좋은 팀원이 또 있을지 걱정이 되는것도 사실이다. (멘토님도 우리 팀처럼 반응이 좋고 참여도 잘하는 팀은 오랜만에 만난다고 하셨다.)  아직은 리액트를 본격적으로 배우기 직전이라 개인적인 계획은 리액트를 배우고 나서 방향성을 정해야 할 것 같다. 멘토님께서도 프론트엔드의 99%는 리액트가 필수라고 말씀하시기도 하셨으니.. 물론 자바스크립트는 계속 공부해야할것같다. 외우는게 어려우니 '눈과 손가락에라도 익도록 반복한다!' 라는 생각이다.

아, 그리고 스프린트 미션이라고 개인과제 제출 미션이 있다. 제출 기한에 제한은 없지만 주어진 디자인을 가지고 코딩해서 제출하는건데 멘토님께서 미션 5는 취업 과제로도 많이 나오는 문제라 꼭 해봤으면 좋겠다고 하셨으니 다음 파트 때는 스프린트 미션을 좀 더 열심히 수행해볼까 한다. (지금은 미션 1만 진행한 상황이다.)

파트1을 함께 해준 동료들에게..

던지는 말 한마디가 너무 웃겼던 갓생사는 ENFP님, 다정다감하고 상황정리를 잘하던 분위기 메이커 INFP님,  조용하지만 잘 어우러져서 천방지축팀을 묵묵히 이끌어줬던 팀장 ISFJ님, 뭔가 범상치 않은 드립과 전공자 + 막내다운 싱싱한 두뇌로 고견을 아낌 없이 나누던 INTJ님, 조금 늦게 합류했지만 잘 적응하고 금새 다른 팀원들을 따라잡던 똑똑이막내 ISTJ님, 그리고 파트1엔 알려줄 것이 많이 없다고 아쉬워 하며 멘토링 시간 늘 알차게 진행해주시던 꼼꼼+깔끔이(a.k.a 알잘딱깔센의 의인화) INTJ멘토님

어느 한명 모남 없이 모두가 둥글둥글하게 잘 어우러져서 더 기억에 많이 남고 아쉬움이 큰 것 같다. 얼굴만 봐도 반가워지는 내적 친밀감이 쌓일 때 쯔음 끝나는 느낌이라 섭섭하지만 다음 만남을 위해서, 그리고 언제든 대화를 나눌 수 있는 채널이 있으니까 섭섭함은 잠시 접어두도록 하겠다. 모두 취업까지 힘내서 열심히 나아갔으면 좋겠습니다.(물론 나도!) ♥팀 화이팅! (멘토님은 다음에 만나는 팀이 멘토님을 많이 힘들게 하지 않길 기원합니다.🙇‍♀️ 하지만 우리팀보단 좋아하지 말아줬으면.. 아냐 그래도 멘토님이 행복하셨으면.. 그래도 우리팀 최고였으면..♾️)

사설 디스코드방에 멘토님을 초대하기 직전에 팀원들끼리 나눴던 대화. 거의 깡패와 다를게 없는것 같지만 착각이다.
사설 디스코드 방의 존재를 멘토링 시간에 밝히고 멘토님도 사설 디스코드 방으로 초대 했던 날
그동안 팀미팅에서 진행했던 팀 미션 주제

+ Recent posts