Skip to content

[실습] JS ES6를 활용하여 Blog Keep 목록 만들기

HYUNSANG HAN edited this page Sep 2, 2019 · 3 revisions

프로젝트 환경 셋팅

webpack 기본 환경설정

  1. npm init : package.json 만들어주는 역할
  2. npm install webpack --save-dev : 웹팩 설치하는 역할. --save-dev를 붙이면 package.json에 심어지게 된다. 개발모드일 땐 -dev를 붙여야 하고, 아니면 생략가능하다.
  3. package.json의 scripts 내부에 "build": "webpack"라고 넣기 : 원래는 node_modules에서 직접 찾아 webpack을 실행해줘야 하는데, package.json의 scripts에 정의해서 실행하기 편하도록 명령어를 만들 수 있는 것임
  4. vi webpack.config.js : webpack.config.js 작성하기. 그러고나서 wq로 닫으면 IDE에서도 이 파일을 가시적으로 확인 가능
var path = require('path');

module.exports = {
  entry: './src/index.js', // bundle로 묶일 대상의 js파일명들
  output : {
  filename: 'bundle.js', // bundle의 결과로 만들어질 js파일명
  path: path.resolve(__dirname, 'dist') // bundle의 결과로 만들어질 js파일이 생성될 경로
  },
  module: {
    rules: [{ // 추후 여기에 규칙을 추가하는 것도 가능
    }]
  }
}
  1. npm run build : 웹팩을 실행시키기 => 그 결과로 bundle.js가 만들어진다!
  2. src 디렉토리를 만든 후 index.js를 그 안에 만들어주기. 테스트를 위해 간단하게나마 js 코드를 작성해둠.
  3. index.html(보여질 화면을 위한 html파일)을 만들고, 아래와 같이 script를 파일 후반부에 추가하여 bundle.js 불러오기
<script src="./list/bundle.js"></script>
  1. html파일을 브라우저에서 열기 => html파일의 내용과 더불어 console창에 "성공이다"가 찍히게 됨 (= 셋팅 완료)

babel 환경설정

  1. npm install babel-preset-env --save-dev : 바벨 설치
  2. npm install babel-loader @babel/core --save-dev : 강좌에서는 이에 대한 설치를 언급하지 않았지만, 1만 설치할 경우 build가 제대로 되지 않는 현상이 있어서 구글링했고 이를 추가로 설치하여 오류를 해결했음
  3. 아래와 같이 babel 관련하여 webpack.config.js에 설정 추가 (참고로, rules 내부에 추가되었음. 그 외에는 변동 없음)
var path = require('path');

module.exports = {
  entry: './src/index.js',
  output : {
  filename : 'bundle.js',
  path : path.resolve(__dirname, 'dist')
  },
  module : { 
    rules : [{
      test : /\.js$/, // 파일이름 정규표현식
      include : path.resolve(__dirname, 'src'), // src디렉토리에 있는 파일들을 대상으로 번들링하겠다는 뜻
      use : {
        loader : 'babel-loader',
        options : {
          presets : [
            ['@babel/preset-env', { // 원래 강의에서는 'env'를 넣으라고 했으나, 모듈 import가 인식되지 않길래 구글링하여 '@babel/preset-env'로 대체함으로써 문제해결함
              'targets' : {
                'browsers' : ["last 2 versions", "ie 9"] // 브라우저별 최근 2개버전, 그리고 명시적으로 ie 9도 타겟으로 트랜스파일링을 해달라는 뜻. 이에 따라, 필요한 플러그인들을 알아서 설치해줌.
              },
              'debug' : true // 참고로, 이건 없어도 상관없음. 다만, 이게 있으면 debug 결과가 콘솔에 찍힘
            }]
          ]
        }
      }
    }]
  }
}
  1. index.js에 ES6문법의 코드를 작성(테스트해보기 위함)
  2. npm run build : webpack 실행. => build가 되어 bundle.js가 생성됨. 들어가보면 ES6를 활용해서 작성했던 코드가 ES5로 변환되어 있는 것을 볼 수 있음.

webpack-dev-server 활용 및 css/js 파일 불러오기

  1. package.json의 scripts안에 "devserver": "webpack-dev-server --inline" 추가 : 참고로 여기서 --inline은 전체 화면을 새로고침해주는 옵션(사실은, 디폴트가 inline이라서 사실 굳이 써줄 필요는 없음)
  2. webpack.config.js의 output안에 publicPath : '/dist'를 설정해줌.(참고로 여기서 dist는 bundle이 담기는 디렉토리명임) devserver는 public path를 중요하게 보기 때문이라고 하며, 여기까지 설정하고나면 js파일을 수정하자마자 업데이트되어 브라우저에 반영되게 됨.
  3. src디렉토리에 안에 css디렉토리를 만들고 index.css만들기
  4. index.html에 <link rel="stylesheet" href="./src/css/index.css">를 추가하여 css파일 불러오기 (참고로, 웹팩로더에 css로더를 설치하면 css파일을 위와같은 방식이 아니라 js에서 import형태로 불러올 수도 있음)
  5. src 아래에 대략 이런 식으로 main.js를 만들기 (import가 잘 되는지 테스트용)
class Blog {
  constructor() {
    console.log('Blog is stated!');
  }
}
export default Blog;
  1. index.js에서 이걸 main.js의 이 class를 import해오기 (참고로 export default는 ES6에 추가된 것인데, 웹팩설정을 해두면 디폴트로 제공되는 기능이라 쓸 수 있음)
import blog from './main.js';
const myBlog = new blog();
  1. npm run devserver : localhost:8080에서 코드가 실시간으로 실행되는 것을 확인 가능

본격적으로, Keep 목록 구현하기

index.html 파일은 아래와 같다. main.js에서 querySelector를 이용하므로, 혹시라도 이거 읽는 분 main.js 읽을 때 참고하시라고 올려둔다.

<!--index.html-->

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>mini-pjt</title>
    <link rel="stylesheet" href="./src/css/index.css">
  </head>
  <body>
    <section class="controller">
      <button class="load-btn">Load</button>
    </section>
    <section class="blog-list">
      <h3>Total List</h3>
      <ul>
      </ul>
    </section>
    <section class="keep-list">
      <h4>Keep List</h4>
      <ul>
      </ul>
    </section>
  </body>
  <script src="./dist/bundle.js"></script>
</html>

이제, main.js를 보자. 참고로 index.js에서는 main.js를 import해오기만 했고, 실질적인 js코드는 main.js에 있다고 보면 된다.

// main.js

class Blog {
  constructor() {
    this.setInitVariables(); // 변수를 정의해주는 것임. 정확히는, blog-list라는 class의 section 하위 ul은 두번 이상 쓰이므로 재활용할 수 있도록 이걸 첫 load 시에 변수로 set해주려는 것임.
    this.registerEvents(); // 클릭이벤트들을 정의해주는 것임.
    this.keptSet = new Set(); // Keep이 눌린 블로그를 모아둘 곳임. 중복을 불허하기 위해 Set 자료형을 사용.
  }

  setInitVariables() {
    this.blogList = document.querySelector(".blog-list > ul"); // blog-list라는 class의 section 하위 ul을 선택해줌.
  }

  registerEvents() {
    const startBtn = document.querySelector(".load-btn"); // load 버튼을 선택해줌.
    const dataURL = "/data/data.json"; // 원래는 외부API의 response를 받아오는 걸로 강의에는 나오지만, API URL이 바뀌어 이와 같이 local의 data를 가져오는 걸로 코드가 추후 변경되었음

    startBtn.addEventListener("click", () => {
      this.setInitData(dataURL); // dataURL에 있는 data를 가져오는 것
    });

    this.blogList.addEventListener("click", ({target}) => { // 여기서 Destructuring이 쓰였다. (event객체를 받아도 됐었지만, 다 받는 것은 불필요하므로 {target}만 받은 것.)
      const targetClassName = target.className; 
      if(targetClassName !== "keep" && targetClassName !=="unkeep") return; // 사실 논리상 안써줘도 그만인 코드지만, 이를 써주지 않으면 '킵해두기'보다 상위 노드를 클릭한 경우에도 EventListener가 작동하는 불편한 점이 있을 수 있음

      const postTitle = target.previousElementSibling.textContent; // 참고로 textContent 대신 innerText라고 써줘도 무방

      if(targetClassName === "unkeep") { // 킵취소를 누르는 경우
        target.className = "keep";
        target.innerText = "킵해두기";
        this.keptSet.delete(postTitle);
      } else { // 킵해두기를 누르는 경우
        target.className = "unkeep";
        target.innerText = "킵취소";
        this.keptSet.add(postTitle);
      }

      this.updateKeptList(); //내 Keep 목록 뷰에 추가.
      //dispatcher.emit("CHANGE_LIKE_LIST", {'title' : this.keptSet}) // <= 만약 이렇게 redux스럽게 코드를 작성한다면 윗줄 대비 의존성이 줄어들기 때문에 더 좋은 코드가 될 여지가 있다는 설명의 예시 코드
    });
  }

  updateKeptList() {
    const ul = document.querySelector(".keep-list > ul");
    let KeptTotal = ""; // Keep리스트를 모아둘 곳

    this.keptSet.forEach ( (contentTitle) => {
      KeptTotal+= `<li> ${contentTitle} </li>`;
    })

    ul.innerHTML = KeptTotal; // forEach를 통해 차곡차곡 모아둔 Keep 리스트를 한번에 innerHTML에 넣음
  }

  setInitData(dataURL) {
    this.getData(dataURL, this.insertPosts.bind(this)); // bind(this)를 써주지 않으면 this가 다르게 인식됨
  }

  getData(dataURL, fn) { // load했을 때 data를 가져오는 것
    const oReq = new XMLHttpRequest();
    oReq.addEventListener("load", () => {
      const list = JSON.parse(oReq.responseText).body;
      fn(list); // 여기서 fn은 사실 insertPosts였던 것.
    });
    oReq.open('GET', dataURL);
    oReq.send();
  }

  insertPosts(list) { // load하여 가져온 data를 .blog-list > ul 내에 그려주는 것
    list.forEach((content) => {
      this.blogList.innerHTML += `
        <li>
          <a href=${content.link}> ${content.title} </a>
          <div class="keep">킵해두기</div>
        </li>
      `;
    })
  }
}

export default Blog;
  • 참고로, debugger라는 것을 코드 내에 끼워놓고 devserver를 run하여 브라우저를 띄우면 그 시점에서 이것저것 console에서 디버깅해볼 수 있다. this가 달라진 것 같다면 debugger를 통해 확인해보자!