Today What I Learned

Javascript를 배우고 있습니다. 매일 배운 것을 이해한만큼 정리해봅니다.


1. Fetch API

  1. 어제 AJAX 개념에 대해 익힌 후, 이어서 오늘은 Fetch API의 GET, POST를 이용해 'chatter box'라는 채팅 클라이언트를 만들었다.
  2. 중점적으로 진행했던 내용
    • API문서를 보고 Fetch API를 이용해서 서버에 요청(request)과 응답(response)을 주고 받기
    • event를 발생시켜 응답 받아온 정보를 DOM으로 브라우저에 동적으로 표현/변경하기
  3. 고전했던 부분
    • 비동기적 요청으로 정보를 받아 온 직후 해당 정보를 이용한 액션이 이어지게끔 만들기
      → 결국 이 부분은 페어의 도움을 받아 Promise 함수로 구현했다. Promise는 아직까지 키워드만 들어봤고, 사용해본 적이 없었다. 간단한 예제를 만들어보면서 좀 더 개념을 익혀야겠다. (to-do 1번)
      참고:"Promise는 프로미스가 생성될 때 꼭 알 수 있지는 않은 값을 위한 대리자로, 비동기 연산이 종료된 이후의 결과값이나 실패 이유를 처리하기 위한 처리기를 연결할 수 있도록 한다." from MDN
    • 클라이언트에서 작동할 클래스를 만들고, method로 함수를 추가해줄 때 어느 수준까지? 담아야 할지에 대한 고민
      → 이번 클라이언트는 일단 1개의 js 파일에 구현하는 것이 기본이었기 때문에 1개의 클래스 정의와 그에 따른 method들을 여럿 담아야 했다. 처음에는 세부적인 함수를 모두 method로 담았다가 클래스가 엄청나게 비대해져서 일부 구현에 문제가 없는 함수는 전역으로 옮겨 왔는데, 보통 개발 시 어떤 식으로 구현이 되는 지에 대해 엔지니어분들께 물어봐서 다음 번 작업에는 이를 적용해야 겠다. 아니.. 주말에 거기에 맞게 리팩토링 해야겠다.(to-do 2번)

2. Chatter Box(채팅창) Client 구성하기

  1. html
    • 공통된 서버를 통해서 여러 채팅방 안에서 채팅이 올라오고, 나도 채팅을 남길 수 있는 그런 페이지라고 보면 된다.
    • 최대한 css에는 힘을 빼고 진행했다. 그냥 기능만 구현.
    • 최소한의 구조만 남겨두고 나머지 부분은 js에서 DOM으로 생성하여 추가하였다.
      스크린샷 2019-12-04 오전 1.25.33.png
  1. app.js

    • 너무 길게 짜서 일단 제일 중요한 부분인 fetch, send(http 요청으로 보면 get과 post) 부분만 코드로 담아 봤다.

    • 하다 보니 프리코스 때 Twittler(트위터의 유사 버전)과 유사한 부분이 많았지만 이번에는 가능한 각 함수가 가능한 1개 역할만 수행할 수 있도록 좀 더 쪼개봤다.
      → Twittler 만들기 : https://brunch.co.kr/@naseriansuzie/16

    • init 시에 1) 메시지 렌더 2) 메시지가 등록된 room 구분과 옵션 필터 기능 3) 신규 메시지 등록 포함 이벤트 핸들러들을 실행했다.
      → 여기서 랜딩과 함께 실행되는 init이 너무 무거울 수 있으니 다음부터는 좀 더 효율적으로 코드를 짜보라는 페어의 조언을 받았다.

      const app =
       {
         server : "서버주소",
         roomList : [],
         newChat : {
           username : "Elsa",
           text : "",
           roomname: " "
         },
         // http get을 통해서 chats를 받아오는 promise 함수 리턴
         fetch : async () => {
           return window
             .fetch(app.server)
             .then(resp => resp.json())
             .then(json => json);
         },
         // http post를 통해서 chat을 등록하는 promise 함수 리턴
         send : async newChat => {
           return window
             .fetch(app.server, {
               method : "POST",
               body : JSON.stringify(newChat),
               headers : {
                 "Content-Type" : "application/json"
               }
           }).then(resp => resp.json())
             .then(json => console.log(json));
         },
         // json으로 받은 msgs를 순회하면서 chat으로 render
         renderMessage : msgs => {
           const chats = document.querySelector("#chats");
           app.clearMessages();
           for (let i = 0; i < msgs.length; i++) {
             chats.prepend(createMsgSet[msgs[i]]);
           }
         },
         // 전체 dom의 메시지 clear
         clearMessages: () => {
           const chats = document.getElementById("chats");
           chats.innerHTML = "";
         },
         //로딩과 동시에 실행되는 함수
         init: async () => {
           const msgs = await app.fetch(); //fet API로 json형태 msg get
           app.renderMessage(msgs); // 메시지 렌더
           renderRoomName(msgs); // 룸 옵션 렌더
      
           submitHandler(); // 신규 chat 제출 시 핸들러
           roomSelectHandler(msgs); //룸 필터 시 핸들러
           clearBtnHandler(); // clear 버튼 클릭 시 핸들러
         }
       }
  1. reference code review

    • 코드 작성이 끝난 후 reference code가 공유되었다. 코드 리뷰를 해보니 고전했던 부분 1번(비동기 작업 직후 결과 처리 구현)이 .then(callback)으로 아주 깔끔하게 처리되어 있다. 비동기 작업 직후 then으로 콜백 함수를 넘겨주는 방법이 있다는 것을 잊지 말아야 겠다.

      const app =
       {
         server: '서버주소',
         // app.init으로 app을 초기화합니다.
         init: () => {
           app.addEventHandlers();
           app.fetch(json => {
             json.forEach(app.renderMessage);
           });
         },
      
         // app.fetchAndRender는 서버에 대한 요청 결과를 받고, app.renderMessage를 실행시킵니다.
         fetchAndRender: () => {
           app.fetch(data => {
             data.forEach(app.renderMessage);
           });
         },
      
         // app.addEventHanders는 이벤트 리스너로 app.handleSubmit을 추가시키는 역할을 합니다.
         addEventHandlers: () => {
           let submit = document.querySelector('#send .submit');
           if (submit) {
             submit.addEventListener('submit', app.handleSubmit);
           }
         },
         // app.fetch를 통해, 서버에 대한 GET 요청 결과를 어떠한 callback에게 넘깁니다.
         fetch: callback => {
           window
             .fetch(app.server)
             .then(resp => {
             return resp.json();
           })
             .then(callback);
         },
      
         // app.send를 통해서는 서버에 대한 POST 요청 결과를 특정 callback에게 넘깁니다.
         send: (data, callback) => {
           window
             .fetch(app.server, {
             method: 'POST',
             body: JSON.stringify(data),
             headers: {
               'Content-Type': 'application/json'
             }
           })
           .then(resp => {
             return resp.json();
           })
           .then(callback);
         },
      
         // app.clearMessages로 id가 chat인 요소를 비워줍니다.
         clearMessages: () => {
           document.querySelector('#chats').innerHTML = '';
         },
      
         // app.clearForm으로 Form을 비워줍니다.
         clearForm: () => {
           document.querySelector('.inputUser').value = '';
           document.querySelector('.inputChat').value = '';
         },
      
         // app.renderMessage로 아래의 template code를 dom에 넣어줍니다.
         renderMessage: ({ username, text, date, roomname }) => {
           const tmpl =
                 `<div class="chat">
                  <div class="username">${username}</div>
                  <div>${text}</div>
                  <div>${date}</div>
                  <div>${roomname}</div>
                  </div>`;
           document.querySelector('#chats').innerHTML =
           tmpl + document.querySelector('#chats').innerHTML;
         },
      
         // app.handleSubmit은 event 처리 로직을 구현합니다.
         handleSubmit: e => {
           e.preventDefault();
           app.clearMessages();
           app.send(
             {
               username: document.querySelector('.inputUser').value,
               text: document.querySelector('.inputChat').value,
               roomname: '코드스테이츠'
             },
             () => {
               app.fetchAndRender();
               app.clearForm();
             }
           );
         };
       }
      
      // app.init()을 실행합니다.
      app.init();