해리의 데브로그

실습 UI 개발을 통해 배워보는 JS & Vue JS (2) - 검색폼 구현 (JS)

|

본 강의는 Inflearn의 김정환 개발자 님의 강의(실습 UI 개발로 배워보는 순수 javascript 와 VueJS 개발)를 듣고 배운 내용을 정리한 포스팅 입니다.

검색폼 구현 (1)

검색 상품명 입력 폼이 위치한다. 검색어가 없는 경우 X 버튼을 숨긴다

1) index.html 코드 작성

  • 자동으로 커서가 갈수 있게 autofocus 속성적용
  • 검색창에 입력한 내용을 삭제하는 기능을 넣기 위해 button 태그를 입력 후 type에 reset 적용
    • btn-reset 클래스 스타일은 style.css에 이미 구현되어 있음.
<div class="container">
    <form>
        <input type="text" placeholder="검색어를 입력하세요" autofocus>
        <button type="reset" class="btn-reset"></button>
    </form>
</div>

검색어가 없는 경우이므로 x 버튼을 숨긴다

2) FormView.js

  • Form의 view를 담당하는 자바스크립트 모듈(FormView.js)을 views 디렉토리 내 생성
  • Object.create를 사용하여 View 객체를 복사함
  • setup 메서드 정의
    • HTML 엘레먼트를 주입받아 내부적으로 속성으로 갖게끔 함(View 함수 중 init이라는 함수 사용)
    • input 엘레멘트와 reset 엘레멘트를 각각 키로 생성
    • 리셋 버튼은 숨기기 위해서 showResetBtn() 라는 함수 생성
  • showResetBtn 함수 정의. show라는 boolean 값을 인자로 받음(default값은 true)
    • resetEl 의 css의 속성에 접근하여 display 속성을 show 일때는 block을 주고, 그렇지 않은 경우는 none을 주는 방식으로 구현
  • FormView를 controller에서 쓰기 위해 export 해줌 (export default FormView)
// views.js 모듈을 복사해서 사용
import View from './Views.js'

// 디버깅 용도로 tag 생성
const tag = '[FormView]'

// 실제 FormView 생성
const FormView = Object.create(View)

FormView.setup = function (el) {
    this.init(el)
    this.inputEl = el.querySelector('[type=text]')
    this.resetEl = el.querySelector('[type=reset]')
    this.showResetBtn(false)
}

FormView.showResetBtn = function (show = true) {
    this.resetEl.style.display = show ? 'block' : 'none'
}

export default FormView

3) MainController.js

  • FormView를 가져와 사용 import FormView from '../view/FormView.js'
  • init 함수에서 FormView.setup 호출
    • 실제 form 엘레먼트를 넘김
import FormView from '../view/FormView.js'

const tag = '[MainController]'

export default {
    init() {
        console.log(tag, 'init()')
        FormView.setup(document.querySelector('form'))
    }
}

검색폼 구현(2)

검색어를 입력하면 x 버튼이 보인다

입력을 했을 때, 이벤트가 발생하고, 이벤트가 발생할 때, 버튼을 보이는 함수가 호출되도록 하면 됨 => FormView.js 내 showResetBtn 함수 호출

1) FormView.js

  • 키보드 입력 이벤트에 대하여 bind해야하므로 bind 이벤트 정의.
  • bind 이벤트를 FormView가 시작되는 setup에서 호출
FormView.setup = function (el) {
    this.init(el)
    this.inputEl = el.querySelector('[type=text]')
    this.resetEl = el.querySelector('[type=reset]')
    this.showResetBtn(false)
   // bindEvent 호출
    this.bindEvents()
    return this
}
  • FormView에서 bindEvents 메서드를 정의
  • inputEl은 HTML 엘레멘트이므로 addEventListener 함수 사용 가능
    • keyup 이벤트를 입력, 이벤트 받아서 처리할 수 있도록 함.
    • onKeyUp 이라는 함수를 따로 정의해서 이벤트를 넘겨주도록 함.
FormView.bindEvents = function() {
    this.inputEl.addEventListener('keyup', e => this.onKeyup(e))
}
  • onKeyup 메서드는 key가 입력됐을 때 마다 실행될 것임. 여기서 x 버튼을 보여줄건지 말지를 정의
    • showResetBtn 함수에 true 값을 넘겨주면 됨.
    • inputEl.value.length : value의 길이를 넘겨주면, 입력한 문자열이 있을 경우 true 값이 넘어감.
FormView.onKeyup = function(e) {
    const enter = 13
    this.showResetBtn(this.inputEl.value.length)
} 

검색폼 구현(3)

엔터키를 입력하면 검색 결과가 보인다

1) FormView.js

  • 키를 입력하는 부분이 onKeyup 함수. 엔터키인지 아닌지를 구별하는 코드 입력.
  • 엔터키는 keyCode가 13으로 정의되어있음 (enter라고 선언한 상수에 저장)
  • 만약에 이벤트의 키코드값이 엔터가 아닌경우 반환을 하고, 엔터인 경우에는 처리를 해줘야함. (엔터키를 입력하면 검색결과가 보인다)
  • 검색결과를 보여주는 것이 FormView의 역할인지를 생각해봐야함. FormView는 검색결과를 보여주는 역할을 하지 않음. 대신에 MainController에게 알려주기만하면됨. MainController는 FormView에서 엔터키가 눌러진다는 것을 확인하고, 다른 View에게 검색결과를 보여주도록 요청을 하면됨. (컨트롤러에게 위임)
  • 따라서 FormView에서는 엔터키가 눌러졌다라는 이벤트를 MainController에게 알려주기만 하면 됨. 이때 사용 하는 것이 View 모델에 있는 emit이라는 메서드 사용.
  • 첫번째 파라미터로는 우리가 정의한 @submit 이라는 이벤트 정의.
FormView.bindEvents = function() {
    this.on('submit', e => e.preventDefault())
    this.inputEl.addEventListener('keyup', e => this.onKeyup(e))
}
  • 파라미터로는 입력한 값을 전달함. (this.inputEl.value)
FormView.onKeyup = function(e) {
    const enter = 13
    this.showResetBtn(this.inputEl.value.length)
    if (e.keyCode !== enter) return
    // todo ....
    this.emit('@submit', {input: this.inputEl.value})

} 

2) MainController.js

  • 컨트롤러에서는 그 이벤트에 대해서 받을 준비가 됨.
  • FormView에 on 메서드가 있음. on 메서드에 우리가 방금 정의했던 @submit 이벤트가 발생했을 때 어떤 함수가 호출되도록 정의해주면 됨. onSubmit 이라는 함수 정의
  • e.detail.input에는 우리가 입력한 값이 넘어오게됨.
export default {
    init() {
        console.log(tag, 'init()')
        FormView.setup(document.querySelector('form'))
        .on('@submit', e => this.onSubmit(e.detail.input))
    },

    onSubmit(input) {
        console.log(tag, 'onSubmit()', input)
    }
}
  • controller에서 setup 함수를 실행한다음 체이닝을 이용하여 on 이라는 함수를 실행했음. 이렇게 할면 setup에서 this를 return 해야함.
FormView.setup = function (el) {
    this.init(el)
    this.inputEl = el.querySelector('[type=text]')
    this.resetEl = el.querySelector('[type=reset]')
    this.showResetBtn(false)
    this.bindEvents()
    return this
}

검색폼 구현(4)

x 버튼을 클릭하거나 검색어를 삭제하면 해당 결과를 삭제한다.

  • x 버튼을 클릭: 클릭이벤트 구현 바인딩 이벤트
  • 검색 결과를 삭제는 FormView의 역할이 아님. 메인컨트롤러에 위임. 이벤트 발생시키자. @reset라는 이벤트를 컨트롤러에게 넘기도록 하자.

1) FormView.js

  • x 버튼은 reset 엘레멘트임. x 버튼에 클릭 이벤트를 바인딩하고 onclickReset 함수 실행
  • FormView에 onclickReset 함수 구현
  • 이 함수에서는 실제 검색결과를 삭제할 필요는 없음.
  • MainController에 reset이라는 이벤트를 전달하기만 함.
  • 실제 검색결과를 삭제하는 역할을 MainController에게 위임.
  • x 버튼을 누르면 버튼이 사라지게 하는 코드 또한 삽입.
FormView.bindEvents = function() {
    this.on('submit', e => e.preventDefault())
    this.on('reset', e => e.preventDefault())
    this.inputEl.addEventListener('keyup', e => this.onKeyup(e))
    // 코드 추가
    this.resetEl.addEventListener('click', e => this.onclickReset(e))
}

FormView.onclickReset = function(e) {
    this.emit('@reset')
    this.showResetBtn(false)

}

2) MainController.js

  • @reset 인 경우, 컨트롤러가 onResetForm이라는 함수 실행
  • 작동 여부를 확인하기 위해 출력 코드 삽입

export default {
    init() {
        console.log(tag, 'init()')
        FormView.setup(document.querySelector('form'))
        .on('@submit', e => this.onSubmit(e.detail.input))
        .on('@reset', e=> this.onResetForm())
    },

    
    onResetForm(input) {
        console.log(tag, 'onResetForm()')
    }    

3) FormView.js

  • backspace로 지웠을 때도 검색 결과가 사라지도록 Main Controller 에게 알려주도록 하자.
  • inputEl 값의 길이가 false, 즉 없을 경우에는 emit으로 controller에게 @reset을 넘김.
FormView.onKeyup = function(e) {
    const enter = 13
    this.showResetBtn(this.inputEl.value.length)
    if (!this.inputEl.value.length) this.emit('@reset')
    if (e.keyCode !== enter) return
    this.emit('@submit', {input: this.inputEl.value})

} 

실습 UI 개발을 통해 배워보는 JS & Vue JS (1) - 준비, 순수 JS 시작하기 (JS)

|

본 강의는 Inflearn의 김정환 개발자 님의 강의(실습 UI 개발로 배워보는 순수 javascript 와 VueJS 개발)를 듣고 배운 내용을 정리한 포스팅 입니다.

1. 준비

1. 개발 환경 구성

  • Node.js
  • 에디터: VS CODE
  • Git (코드형상관리)
  • Chrome 최신버젼
  • Lite Server - 개발 서버. 홈페이지를 개발시, 개발용 서버로 사용되는 툴
    • Global 설치
$ npm install -g lite-server
 
# To run: 
$ lite-server

2. 요구사항 분석

  • 검색폼 구현
  • 검색 결과 구현
  • 탭 구현
  • 추천 검색어 구현
  • 최근 검색어 구현

2. 순수 JS (MVC) 시작하기

1. MVC 패턴 설명

MVC는 Model, View, Controller으로 구성되어있음.

  • Model: 데이터를 관리하는 역할. DB 데이터를 갖고와서 또다른 객체에게 전달하는 역할. 반대로 외부 객체로부터 입력데이터를 받아서 넣는 역할도 함. 웹프론트에서 모델의 역할은 데이터베이스에 직접 접근하지 않고, API의 형태로 접근함 API의 형태로 데이터를 갖고와 다른 객체에 전달하는 역할을함. 외부 객체로부터 데이터를 입력받으면, 그 데이터를 백엔드 API를 통해서 호출하는 역할을 함.
  • View: 데이터를 가지고 화면을 관리하는 역할. Model의 데이터를 화면에 그리는 역할을 함. (HTML, CSS, JS로 구성되어있음). 사용자가 입력한 데이터를 처리하는 역할도 함. 입력하면 입력 이벤트를 받아서 입력된 값을 다를객체에게 전달하는 역할을 함.
  • Model과 View는 직접적으로 연결되어 있지 않음. 이 둘을 연결하는 역할을 하는게 controller. controller는 Model로부터 데이터를 갖고 온 후, View에게 전달해줌. 반대로 View로부터 사용자 입력데이터를 얻고 그 데이터를 Model에게 전달하는역할도 함. controller는 Model과 View를 둘다 관리하는 역할을 함.

2. 기본 폴더 구조

1) 기본 폴더 구조

│  index.html
│  style.css
│  
└─js
    │  app.js
    │  
    ├─controllers
    │      MainController.js
    │      
    ├─models
    │      HistoryModel.js
    │      KeywordModel.js
    │      SearchModel.js
    │      
    └─views
            View.js

2) views/View.js

  • 앞으로 만들게 될 Vue는 View.js module을 기반으로 만들게 됨.
const tag = '[View]'

export default {

  // init은 el(element)를 주입받음.
  init(el) {
    if (!el) throw el
    // el을 자신의 property로 받게됨
    this.el = el
    return this
  },

  // On: 특정 이벤트에 대한 행동을 정의함. 이벤트와 핸들러를 같이 인자로 받음. 
  on(event, handler) {
    // 현재 가지고 있는 엘레멘트에서 특정 이벤트가 발생했을 때 handler가 실행되도록하는 역할
    // handler: vue를 사용 시, vue에서 나온 이벤트를 어떻게 핸들링 할 것인가를 위해서 사용을 한 것
    this.el.addEventListener(event, handler)
    return this
  },

  // Emit: 스스로 이벤트를 방출하는 기능을 함. 이벤트와 데이터를 인자로 받음.
  emit(event, data) {
    // customEvent는 첫번째 인자로 event 이름을 받고, 
    // 2번째 인자로 디테일 키를 갖고 있는 데이터 객체를 받음
    const evt = new CustomEvent(event, { detail: data })
    
    // HTML el(element)에는 dispatchEvent라는 메서드가 있음. 인자로 방금 만든 evt 객체를 넘겨줌.
    // 이를 통해 el는 이벤트를 방출함.
    this.el.dispatchEvent(evt)
    return this
  },

  // css 
  hide() {
    // css 속성 중 display 값이 none으로 주면 hide가 됨
    this.el.style.display = 'none'
    return this
  },

  show() {
   	// css 속성 중 display 값을 주지않으로 주면 show
    this.el.style.display = ''
    return this
  }
}

3) models/HistoryModel.js

  • 검색 히스토리에 대한 데이터를 관장함.
export default {
  // 데이터를 collecction의 형태로 갖고 있음.
  // keyword: 검색어, data: 검색날짜
  data: [
    { keyword: '검색기록2', date: '12.03' },
    { keyword: '검색기록1', date: '12.02'},
    { keyword: '검색기록0', date: '12.01' },
  ],

  // ㅣist 메서드는 데이터를 return 해줌. 특별하게는 Promise 패턴을 이용함. return this.data를 하지 않고 Promise 패턴을 사용한 이유는 history 모델의 경우 서버에서 비동기로 갖고 오는 경우도 있고, 쿠키로도 데이터를 얻을 수도 있기 떄문에 공통적으로 사용하기 위해 Promise 패턴 사용
  list() {
    return Promise.resolve(this.data)
  },
  
 // 추가될 검색어를 받아(keyword='') 실제 데이터가 데이터에 있는지를 체크한 후, 있으면 삭제, 다시 날짜를 산정하여 기존데이터와 합쳐서 추가하는 기능
  add(keyword = '') {
    keyword = keyword.trim()
    if (!keyword) return 
    if (this.data.some(item => item.keyword === keyword)) {
      this.remove(keyword)
    }

    const date = '12.31'
    this.data = [{keyword, date}, ...this.data]
  },
  
 // keyword를 받아서 그 키워드에 해당하는 것들을 삭제하는 기능
  remove(keyword) {
    this.data = this.data.filter(item => item.keyword !== keyword)
  }
}

4) models/KeywrodModel.js

  • 추천 검색어를 보여주기 위한 모델
export default {
  // 데이터를 콜렉션의 형태로 갖고 있음. keyword만 갖고 있음.
  data: [
    {keyword: '이탈리아'}, 
    {keyword: '세프의요리'}, 
    {keyword: '제철'}, 
    {keyword: '홈파티'}
  ],

  // 리스트 메서드만 갖고 있음. Promise 패턴을 이용. setTimeout을 이용하여 200ms 이후 데이터를 반환하도록 구현
  list() {
    return new Promise(res => {
      setTimeout(() => {
        res(this.data)
      }, 200)
    })
  }
}

5) models.SearchModel.js

  • 검색하는 모델
const data = [
  {
    id: 1,
    name: '[키친르쎌] 홈메이드 칠리소스 포크립 650g',
    image: 'https://cdn.bmf.kr/_data/product/H1821/5a4ed4e8a6751cb1cc089535c000f331.jpg'
  },
  {
    id: 2,
    name: '[키친르쎌] 이탈리아 파티 세트 3~4인분',
    image: 'https://cdn.bmf.kr/_data/product/H503E/300d931e3b8252ed628b6a3c2f56936b.jpg'
  }
]

export default {
  // 리스트 메서드에 쿼리를 인자로 받아 쿼리에 해당하는 데이터를 return 하도록 코드 구현
  // setTimeout을 이용하여 200ms 이후 데이터를 반환하도록 구현
  list(query) {
    return new Promise(res => {
      setTimeout(()=> {
        res(data)
      }, 200);
    })
  }
}

6) 서버 실행

  • Root directory 에서 lite-server 로 서버 실행

7) Header 구현

  • index.html > body > 내 div & header를 통해 헤더 구현.
  • h2 태그에 container 클래스 속성 적용
<body>
  <div>
    <header>
      <h2 class="container">검색</h2>
    </header>
  </div>
  <script type="module" src="./js/app.js"></script>
</body>
</html>

8) Controllers 생성

  • js 디렉토리 아래 controllers 디렉토리 생성 및 MainController.js 파일 생성
  • debugging을 위해 sample code 입력
  • app.js에서 MainController.js를 import 한 후, DOM이 전부 로드 되는 이벤트가 발생하였을 때, MainController의 생성자메서드가 실행되도록 EventListener 구현
// MainController.js
const tag = '[MainController]'

export default {
    init() {
        console.log(tag, 'init()')
    }
}

//app.js
import MainController from './controllers/MainController.js'

document.addEventListener('DOMContentLoaded', ()=>{
    MainController.init()
})

Vue.js 04 - Vue의 속성(Computed, watch) / v-text & v-html / v-if & v-show

|

1. Vue의 속성(Computed, watch)

1) Computed

  • 어떠한 계산된 결과를 미리 담아 놓는 Vue 클래스의 속성
  • 앞 포스팅에서 구현한 간단한 To do 앱 methods 에서 구현한 todoByStatus 함수를 computed로 이동
  • 반복문을 todosByStatus() => todosByStatus로 변경
<!-- 변경 전 -->
<li v-for="todo in todosByStatus()" v-bind:key="todo.id">
<!-- 변경 후 -->
<li v-for="todo in todosByStatus" v-bind:key="todo.id">

1-1) Computed 사용 예시

  • 새로운 Todo를 나타내는 newTodo를 거꾸로 출력하는 코드를 DOM에서 작성하면 아래와 같음
<span> </span>
  • 그러나 DOM에 어떠한 기능에 대한 로직을 작성하는 것은 일반적으로 권장되지 않으며, 로직에 대한 부분을 바깥쪽, computed 속성에 작성함. 복잡한 자바스크립트 표현식을 HTML에 끼워넣기 보다는 computed 속성안에 코드를 작성하는 것이 선호됨.
<span> </span>

<script>
    const app = new Vue ({
        // 중략
        computed: {
            reverseNewTodo: function(){
                return this.newTodo.split('').reverse().join('')
            },
        }
    })
</script>

2) methods vs computed

  • methods와 computed 간 차이의 핵심은 캐싱의 유무
  • computed는 한번 계산을 해놓고, 계산한 결과를 캐싱해놓음(저장해 놓음). 한번더 함수를 실행시키는 것이 아니라, 저장된 값을 불러와 그대로 사용함.
  • 하기 예시에서 Toggle Rendering을 눌러서 사용하면, methods는 보여질때마다 사용함. 그러나 computed는 내부적으로 변화한 부분이 없으므로 이전에 계산했던 값을 그대로 사용함.
  • 내부적으로 값을 변경할 때 methods를 사용함.
<div id="app">
    <button v-on:click="visible=!visible"> Toggle Rendering </button>
    <ul v-if="visible">
		<li>dateMethod: </li>
        <li>dateComputed: </li>
    </ul>
</div>
<script>
    const app = new Vue ({
        el: '#app',
        data: {
            visible: true,
        },
        methods: {
            dateMethod: function(){
                return new Date()
            },
        },
        computed: {
            dateComputed: function(){
                return new Date()
            }
        }
    })
</script>

3) watch

watch 속성은 어떠한 값이 변경디면 함수가 실행되게 할 때 사용함. 어떠한 값이 변경이 되었을 때 이 함수가 오래 걸리거나, 언제 끝날지 모르는 경우에 주로 사용.

Yes or No API를 사용하여 Watch 속성에 대해 살펴보도록 하자.

  • 하기 예시의 경우, qeustion 값이 바뀌면, watch 내 question 함수가 실행됨.

  • Yes or No API에 요청을 보내는 것을 methods 내 함수로 구현

    • 우리가 입력한 질문이 ?으로 끝날 경우, API에 요청을 보내도록 분기문 적용
    • answer의 값에 API를 통해 반환되는 response.data.answer 값을 입력
<div id="app">
    <h1>질문을 입력하세요</h1>
    <input type="text", v-model="question">
    <p>  </p>
</div>
<script>
    const app = new Vue ({
        el: '#app',
        data: {
            question: '',
            answer: '',
        },
        watch: {
            question: function(question){
                console.log(question)
                this.getAnswer()
            }
        },
        methods: {
            getAnswer: function(question){
                if (this.question[this.question.length-1] === "?"){
                    axios.get('https://yesno.wtf/api')
                    	.then((response)=>{
                        	console.log(response)
                        	this.answer = response.data.answer
                    })
                }
            }
        }
    })
</script>
  • yesorno API를 통해 yes & no 랜덤 사진도 갖고올 수 있음.
    • data 내 이미지 경로를 담을 imageUrl 정의
    • API를 통해 return 되는 이미지 경로를 imageUrl에 저장(this.imageUrl = response.data.image )
<body>
    <div id="app">
        <h1>질문을 입력하세요</h1>
        <input type="text" v-model="question">
        <p> </p>
        <img v-bind:src="imageUrl">
    </div>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                question: '',
                answer: '',
                imageUrl: '',
            },
            watch: {
                question: function (question) {
                    console.log(question)
                    this.answer='생각중입니다....'
                    this.getAnswer()
                }
            },
            methods: {
                getAnswer: function (question) {
                    if (this.question[this.question.length - 1] === "?") {
                        axios.get('https://yesno.wtf/api')
                            .then((response) => {
                                console.log(response)
                                this.answer = response.data.answer
                                this.imageUrl = response.data.image
                            })
                    }
                }
            }
        })
    </script>
</body>

2. v-text vs v-html

  • v-text 는 그 값을 단순문자열로 취급함. 하기 코드의 경우, ``를 썼을 때와 같은 결과를 보여줌 - '<h1> 제목입니다 </h1>'
  • v-html의 경우, 실제 HTML으로 rendering하여 화면에 보여줌.

3. 조건부 렌더링(v-if vs v-show)

  • v-if는 조건을 만족하지 못할 경우, 코드가 html에서 사라짐
  • v-show의 경우, 속성값을 적용하여 안보이게 하나, 코드는 남아있음. v-show가 있는 엘리먼트는 항상 렌더링 되고 DOM에 남아있음. v-show는 단순히 엘리먼트에 display CSS 속성을 토글함.
<div id="app">
    
    <div v-text="header"></div>
    <div v-html="header"></div>

    <h1 v-if="visible">v-if</h1>
    <h1 v-show="visible">v-show</h1>

</div>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            header: '<h1> 제목입니다 </h1>',
            visible: true,
        }
    })

</script>

Vue.js 03 - To do 앱 확장하기

|

To do 앱 확장하기

1) v-model 디렉티브

  • data 내 새로운 todo의 데이터가 저장될 newTodo를 추가
  • 새로운 todo를 입력한 input 태그를 body 내에 추가

  • v-model 디렉티브를 통해 input 태그에서 입력된 값이 Vue 인스턴스 내 data 안의 newTodo와 동기화시킬 수있음 (양방향 데이터 바인딩 생성)
<body>
    <div id="app">
	    <!-- 중략 -->
            <div>
                <input type="text" v-model="newTodo">
            </div>
    </div>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                newTodo: '',
               // 중략
            },
        })
    </script>
</body>
</html>

2) To do 리스트에 새로운 항목 추가하기

JS 코드를 이용하여 newTodo에 적힌 값을 todos에 값을 추가해보도록 하자

  • vue 클래스 내 methods에 addTodo 함수 생성
    • input 내 입력된 값이 todo 리스트에 추가하도록 코드 구현
    • newTodoinput 태그가 양방향 바인딩 되어있는 것을 활용하여 newTodo="" 을 입력하여 input 태그 안의 값을 초기화 시킴
  • input 태그 내 v-on:keyup.enter 을 사용하여, 엔터키를 눌렀을 때, 이벤트가 발생하게 지정
    • enter / tab / delete 등을 keyup. 이하에 붙일 수 있으며, keycode(숫자)로도 표현이 가능함
    • 또는 button 태그와 마우스 클릭을 이용하여 이벤트가 발생하게도 지정할 수 있음.
<body>
    <div id="app">
        <ul>
            <li v-for="todo in todos" v-if="!todo.completed" v-on:click="check(todo)">
                
            </li>
            <li v-else v-on:click="check(todo)"> [완료]</li> 
        </ul>
        <div>
            <input type="text" v-model="newTodo" v-on:keyup.enter="addTodo">
            <button v-on:click="addTodo"> 추가하기 </button>
        </div>
    </div>
    <script>
        const app = new Vue ({
            el: '#app',
            data: {
                newTodo: ""
                todos : [
                    {
                        content: '점심메뉴 고민하기',
                        completed: true,
                    },
                    {
                        content: '사다리타기',
                        completed: false,
                    },
                    {
                        content: '약속의 2시 낮잠자기',
                        completed: false,
                    },
                    {
                        content: '야자하기',
                        completed: false,
                    }  
                ]
            },
            methods: {
                check: function(todo){
                    todo.completed = !todo.completed
                },
                addTodo: function(){
                    this.todos.push({
                        content: this.newTodo,
                        completed: false,
                    })
                    this.newTodo = ""
                }
            }
        })
    </script>
</body>

3) To do 리스트에 삭제 기능 구현

  • filter 메서드를 이용하여 원본데이터를 건들지 않고 새로운 배열을 만듦.
  • 완료 된 것을 삭제한 것이 아니라, 완료되지 않은 것들을 filter로 보여주는 방식으로 구현
  • todosnotCompletedTodos로 새로 저장
  • 익명 함수 내 새로운 함수로 구현할 때는 Arrow Function을 써야함
<footer>
    <button v-on:click="clearCompleted">Clear Completed</button>
</footer>
methods: {
    clearCompleted: function(){
        const notCompletedTodos = this.todos.filter((todo)=>{
            return !todo.completed
        })
        this.todos = notCompletedTodos
    },
}

4) 완료 표시 형태 변경

  • v-model"todo.completed" 을 적용하여, 체크박스와 completed를 동기화시킴.
  • 하나의 체크박스는 단일의 boolean 값을 가지는 것을 활용하여, completed의 값이 true일 때, 체크박스에 체크가 되도록 구현
<div id="app">
    <!-- 체크박스 사용 코드 -->
    <li v-for="todo in todos">
        <input type="checkbox" v-model="todo.completed">
        <span></span>
    </li>
    <ul> 
    <!-- 기존 코드 -->
        <li v-for="todo in todos" v-if="!todo.completed" 
            v-on:click="check(todo)">
			
		</li>
		<li v-else v-on:click="check(todo)">[완료!]</li>
    </ul>
</div>

5) 취소선 구현

  • head > style 태그 내 completed 클래스에 대한 스타일 적용가능
  • v-bind를 사용 (v-bind 는 동적으로 하나 이상의 컴포넌트 속성 또는 표현식에 바인딩함)
  • 속성 값인 "" 에는 자바스크립트 코드가 들어갈 수 있으므로, 삼항 연산자를 사용하여 조건문을 구현 할 수 있음.
    • 삼항연산자 - ? 앞에는 조건식을 적음. 조건 만족 시, ? 바로 뒷 값 적용. else 부분은 : 뒤에 작성
    • 이를 통해 todo.completed 값이 true 인 데이터에 대하여 스타일을 적용 시 킬 수 있음
<head    
	<style>
        .completed {
            text-decoration: line-through;
            opacity: 0.6;
        }
    </style>
</head>
<div id="app">
    <li v-for="todo in todos">
        <input type="checkbox" v-model="todo.completed">
        <span v-bind:class="todo.completed ? 'completed' : ''">
            
        </span>
    </li>

6) 취소선 구현 (2)

삼항 연산자는 단 하나의 조건만을 적용시킬 수 있다는 단점이 있음. 그러나 객체(오브젝트) 형태로 구현하면 여러 조건을 적용시킬 수 있음.

  • 오브젝트 형태로 value에는 조건식을, key에는 조건에 대한 결과를 적음
<li v-for="todo in todos">
    <input type="checkbox" v-model="todo.completed">
    <span v-bind:class="{completed:todo.completed}"> </span>
</li>

cf) 객체를 통한 스타일 속성 정의

  • 동적으로 스타일을 입히기 위해서 사용함
  • font-size 처럼 속성에 - 이 들어간 경우, JS 표현식으로 입력할 때는 camelCase로 표현함 (=> fontSize)
  • 새로운 Todo를 입력하는 input 태그에 color 속성 값(예; red)를 입력하면 스타일이 변경됨을 알 수 있음.
<div v-bind:style="{color:newTodo, fontSize:'30px'}">
    <span> Red Text, 30 px </span>

</div>

7) 셀렉트 박스 활용

셀렉트 박스를 통해 “완료된 리스트 / 완료되지 않은 리스트 / 모든 리스트”를 선별적으로 볼 수 있게 코드를 구현해보도록 하자.

  • Vue-data 내 셀렉스 박스 내 목록으로 사용할 status를 추가 (status에 따라 다른 리스트를 보여주도록 구현)
  • methods 내 셀렉트 박스 내 선택된 항목에 따라 다른 리스트를 보여주기 위한 함수 todosByStatus를 정의
    • this.statusactive 인 경우, filter를 이용하여 todo.completedfalse 즉, 완료되지 않은 리스트를 반환함.
    • 반대로, this.statuscompleted인 경우, 완료된 리스트를 반환
data: {
    status: 'all'
    // 중략
}
methods: {
    // 중략
    todosByStatus: function(){
        
        // 아직 완료되지 않은 리스트
        if (this.status === 'active'){
            return this.todos.filter((todo)=>{
                return !todo.completed
            })
        }
        
        // 완료된 리스트
        if (this.status == 'completed'){
            return this.todos.filter((todo)=>{
                return todo.completed
            })
        }
        
        // 전체리스트
        return this.todos
    },
}
  • status와 바인딩할 또다른 input이 필요함. 이때 셀렉트 박스를 사용
  • 반복문은 todos에서 todosByStatus()로 변경. todosByStatus 의 마지막 return이 전체 리스트인 this.todos를 나타내므로 변경해도 달라지는 부분은 없음.
  • 셀렉트 박스를 통해 선택된 value에 따라 methods에 정의된 todosByStatus 의 조건문이 적용되며, 그에 따라 반복문이 선별적으로 돌면서 사용자는 다른 리스트를 볼 수있게 됨.
<div id="app">
    <select v-model="status">
        <option value="all"> all </option>
        <option value="active"> active </option>
        <option value="completed">completed</option>
    </select>
    <ul>
        <li v-for="todo in todosByStatus()">
            <input type="checkbox" v-model="todo.completed">

8) v-bind: key=""

지금까지 구현한 코드를 기반으로 체크박스에 체크를 누를 경우, 선택된 목록이 셀렉트박스 및 todosByStatus 함수에 따라 이동이 되나, 체크가 그대로 남아있는 등, 의도치 않는 동작이 발생함을 알 수 있음.

4개의 todo가 있는 경우, 반복문을 통해서 4개의 li 태그가 생기는데, 나중에 어떤 사용자가 내용을 추가하거나 변경하는 경우 vue는 새롭게 내부적으로 브라우정 렌더링을 해야겠구나 것을 앎. 이때 vue는 key값을 기반으로 인지를 함. key는 DB의 기본키처럼 고유의 인덱스 값을 나타내는 역할을 함. key를 기준으로 어떤 위치의 값을 변경하고 삭제하는 등의 동작을 수행함.

따라서, v-for를 통해 반복문을 돌리는데, 반복문 내 개별적인 데이터에 key라는 속성을 부여해야함. 이때 사용하는 것이 v-bind:key="" 임. key값을 주지 않으면 vue는 혼란이 옴. 의도치 않은 동작이 생기는 이유는 고유한 인덱스 값인 key 값을 부여하지 않아서 발생하는 것이므로, key 값을 부여하도록 하자.

  • todos 안 todo들에게 id값을 부여
  • 새로운 todo를 추가하는 함수인 addTodo에서 id값을 부여할 때는, 중복되지 않은 아이디 값을 적용할 때는 시간 개념을 활용하자 Data.now()
<li v-for="todo in todosByStatus()" v-bind:key="todo.id">
    <input type="checkbox" v-model="todo.completed">
data: {
    	status:'all',
        newTodo: '',
        todos: [
            {
            //추가된 부분//
                id: 1,
                content: '점심 메뉴 고민하기',
                completed: true,
            },
            
methods: {

    addTodo: function(){
        this.todos.push({
            //추가된 부분//
            id: Date.now(),
            content: this.newTodo,
            completed: false,
        })
        this.newTodo = ""
    },

Vue.js 02 - To-do 앱 구현 & Cat API 사용하기

|

To-do 앱 구현 & Cat API 사용하기

1. 간단한 To-do 앱 구현

1) 반복문 & 조건문 동시 활용 하기

  • data 내 todo 리스트를 오브젝트 형태로 구현
  • Case 1
    • 를 통해 content만 출력 할 수 있음
  • Case 2
    • 조건문과 반복문을 둘다 사용하여 completed:false 만 출력 가능
    • v-for 가 항상 v-if 보다 우선순위가 높음
    • else 조건은 v-else를 사용하여 표현 가능. 혹은 v-else-if로도 표현 가능
<div id="app">
    <ul>
        <!-- case 1 -->
        <li v-for="todo in todos">
            
        </li>
          
        <!-- case 2 -->
        <li v-for="todo in todos" v-if="!todo.completed">
            
        </li>
        <li v-else-if="true">[완료]</li>
        <li v-else> [완료!] </li>
    </ul>
</div>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            todos: [
                {
                    content: '점심메뉴 고민하기',
                    completed: true,
                },
                {
                    content: '사다리타기',
                    completed: false,
                },
                {
                    content: '약속의 2시, 낮잠자기',
                    completed: false,
                },
                {
                    content: '야자하기',
                    completed: false,
                }
            ]
        }
    })
</script>

2) Vue.js 를 활용한 EventListener 구현 (1)

  • v-on 사용
  • html 태그에 “이걸 누르면 실행하라!” 라고 구현 가능.
    • v-on:click="todo.completed = true" : 클릭이 이루어지면 todo.completed의 값을 true로 변경
<div id="app">
    <ul>
        <li v-for="todo in todos" v-if="!todo.completed" v-on:click="todo.completed = true">
            
        </li>
        <li v-else> [완료!] </li>
    </ul>
</div>

3) Vue.js를 활용한 EventListener 구현 (2)

  • 더 나아가, v-on:click 의 값으로 methds 내 함수를 호출함으로써 더 간결한 코드를 작성할 수 있음.
  • methods 내 check 라는 이름을 가진 함수를 정의. 함수를 통해 todo.completed = !todo.completed 를 작성. todo.completed 값을 반전시킴(true인 경우는 false로, false인 경우는 true로 반전)
  • 완료된 경우, todo 내용이 다시 뜨도록, v-else 조건 뒤에 동일한 이벤트 리스너 적용
<div id="app">
    <ul>
        <li v-for="todo in todos" v-if="!todo.completed" 
            v-on:click="check(todo)">
            
        </li>
        <li v-else v-on:click="check(todo)"> [완료!] </li>
    </ul>
</div>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            todos: [
                {
                    content: '점심메뉴 고민하기',
                    completed: true,
                },
                {
                    content: '사다리타기',
                    completed: false,
                },
                {
                    content: '약속의 2시, 낮잠자기',
                    completed: false,
                },
                {
                    content: '야자하기',
                    completed: false,
                }
            ]
        },
        methods: {
            check: function(todo){
                todo.completed = !todo.completed
            }
        }
    })
</script>

4) Vue.js devtools 설치

  • Vue.js 디버깅을 위한 chrome 확장 프로그램으로 Vue.js devtools 설치

  • chrome - 도구더보기 - 확장프로그램 관리 - 세부정보 - 파일 URL에 대한 엑세스 허용
  • F12 눌린 후, 개발자 도구 탭의 끝으로 가면 Vue 라는 새로운 메뉴가 생긴 것을 알 수 있음.

2. Cat API를 Vue에 적용하기

고양이 사진을 random으로 보여주는 웹 사이트가 있음. 이 사이트는 외부에서도 사용할 수있는 API를 제공하고 있는데, Vue.js 를 통해 API에 요청을 보내고 응답으로 사진을 받는 코드를 동적으로 구현해보자.

1) 기본 코드 작성

  • Vue.js & Axios CDN을 head 태그에 추가
  • 이미지 크기를 고정시키기 위해 style 태그를 head에 추가한 후, width를 400px으로 고정
  • data 내 이미지의 url을 저장시킬 imageURL 추가. methods에서 정의한 함수를 통해 갖고오는 url을 해당 변수에 저장시킴.
  • methods에는 cat API를 통해 이미지를 갖고오는 함수를 정의. axios를 사용하자!
<head>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <style>
        img {
            width: 400px;
        }
    </style>
</head>
<body>
    <div id="app">
    </div>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                imageURL:'',
            },
            methods: {
                }
            }
        })
    </script>
</body>

2) 코드 확장

  • 버튼과 사진을 보여줄 위치 지정
  • v-on:click의 값으로 Vue 인스턴스 메서드 안에 정의한 getCatImage() 함수 입력. 이를 통해, 버튼 클릭 시 함수가 실행됨.
  • axios.get의 인자로, cat API 주소를 삽입함. API 주소의 응답정보를 출력 해보면, resonse.data[0].url에 고양이의 사진 이미지 경로가 저장되어있음을 알 수 있음. 경로를 갖고 온 후, imageURL 값에 저장
  • 이때 axios의 return 값인 .then 이하는 arrow function으로 구현할 것.
  • 이미지의 주소를 변수로 작성할 때는 v-bind를 사용해야함. v-bind는 html 속성에 vue instance의 값을 사용하기 위한 용도로 쓰임. html 속성(attributes)에는 인터폴레이션(interpolation)으로 값을 직접 넣지 못하므로 디렉티브 v-bind를 사용함.
<body>
    <div id="app">
        <button v-on:click="geetCatImage()"> 고양이 사진 보기 </button>
        <div>
            
            <img v-bind:src="imageURL" alt="cat Image">
        </div>        
    </div>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                imageURL:'',
            },
            methods: {
                getCatImage: function(){
                    axios.get('https://api.thecatapi.com/v1/images/search')
                    	.then((response)=>{
                        	return this.imageURL = response.data[0].url 
                    })
                }
            }
        })
    </script>
</body>

3) Arrow Function 내 this

자바스크립트의 this는 일반적인 프로그래밍 언어에서의 this와 조금 다르게 동작함. java this와 python self의 인스턴스의 호출한 대상의 현재 객체를 뜻하는 것(참조)이었다.

하지만 자바스크립트의 function에서는 함수가 어떻게 호출되었는지에 따라 동적으로 동작함.

  • 기본적인 함수 선언 후 this 출력 시, 전역 객체(window)를 호출됨.
  • 메서드를 선언하고 호출할 경우, 오브젝트의 메서드이므로 오브젝트가 호출됨.
  • arrow function에서의 this는 호출과 위치와 상관없이 상위 스코프 this를 가리킴.

위의 예시에서, .then 이하의 익명함수를 일반적인 함수의 형태로 구현할 경우, this는 전역 객체인 window를 가리킴. 이러한 불편함을 해결하기 위해 arrow function을 이용하여 this (Vue 인스턴스)를 그대로 사용 할 수 있게 됨.