본문 바로가기

FrontEnd/VanillaJs

[Frontend] VanillaJS vs Vue.js (1)



어제까지 ES6에 관한 책을 읽고 나니,


내가 여태 까지 인강을 보며 짠 코드들을 얼마나 이해하지 않고 받아 적었는지


그리고 지금 보면 보이지 않는 것들이 보이지 않을까 싶어


강정환 강사님의 순수 자바스크립트와 Vue.js 강의를 다시 정리해보기로 했다.



다음 형태의 검색어에 따라 View가 바뀌는 앱이며,

MVC패턴을 지닌다.



컴포넌트로 분류하면

검색어를 받는 컴포넌트

추천검색어와 최근검색어의 탭이 있는 컴포넌트

추천검색어를  클릭시에 나오는 컴포넌트

최근검색어를 클릭시에 나오는 컴포넌트

결과(제품)이 나오는 컴포넌트

그리고 전체를 총괄하는 컴포넌트


가 있고


추천검색어, 최근 검색어, 검색결과는 각각 데이터를 지닌다.




app.js

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

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


앱은 문서를 불러오면 실행되고 이는 MainController의 init()을 실행시킨다.


MainController.js

export default {
init() {
FormView.setup(document.querySelector('form'))
.on('@submit', e => this.onSubmit(e.detail.input))
.on('@reset', e => this.onResetForm())
TabView.setup(document.querySelector('#tabs'))
.on('@change', e => this.onChangeTab(e.detail.tabName))
KeywordView.setup(document.querySelector('#search-keyword'))
.on('@click', e => this.onClickKeyword(e.detail.keyword))
HistoryView.setup(document.querySelector('#search-history'))
.on('@click', e => this.onClickHistory(e.detail.keyword))
.on('@remove', e => this.onRemoveHistory(e.detail.keyword))

ResultView.setup(document.querySelector('#search-result'))

this.selectedTab = '추천 검색어'
this.renderView()
}


메인 컨트롤러는 모든 View의 기본동작들을 실행시킨다.


예를 들어 FormView에선

1. 검색어를 입력해서 Model과 View 사이의 데이터를 전송하는 submit과

2. x버튼을 누르면 검색어 창이 초기화 하는 reset을


TabView에서는

1. 최근 검색어나 추천 검색어를 클릭 할 때 아랫 부분에 어떤 뷰를 렌더링할지 선택하는 change를


KeywordView에서는

1. 클릭시에 검색 결과를 나타내는 click


History에서는

1. 역시 클릭시에 검색 결과를 나타내는 click

2. 그리고 x버튼을 누르면 기록이 사라지는 remove


와 같은 실행을 관리하고


모든 View는 setup이라는 메소드를 각각의 element에 맞춰 실행시킨다.


FormView.js

import View from './View.js'

const tag = '[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)
this.bindEvents()
return this
}


FormView의 setup객체의 함수는 다음과 같다.


View라는 인스턴스?클래스?에 있는 기능들을 가져와 FormView라는 Object를 생성한다.


그리고 FormView에 사용되는 엘리멘트들을 등록하고

함수들을 각각 실행한다.


이때, init()과 같은 함수들은 모든 View에서 사용되기 때문에 View.js에 만들어져 있다.


const tag = '[View]'

export default {
init(el) {
if (!el) throw el
this.el = el
return this
},

on(event, handler) {
this.el.addEventListener(event, handler)
return this
},

emit(event, data) {
const evt = new CustomEvent(event, { detail: data })
this.el.dispatchEvent(evt)
return this
},

hide() {
this.el.style.display = 'none'
return this
},

show() {
this.el.style.display = ''
return this
}
}


init()

 element를 받고

element가 없으면 그것을 예외로 처리해서라도

각각의 View 객체에 element를 추가하는 것이다.


이는 이 객체 내부에 어떤 element가 속성으로 되어 있지 않기 때문에 그 element와 자식 element를 접근하기 위해서 사용된다.


on(), emit()

emit()은

이벤트를 추가하는 것이다.

CustomEvent를 통해서 어떤 이벤트를 생성하고 이는 이벤트와, 속성 및 속성값을 가진다.

on()은

emit()이든 새로운 이벤트든

이벤트 발생시에 핸들러를 컨트롤 하는 것이다.


예를 들면

기존에 SubmitButton이 있을 때 이를 클릭할 때 click 이벤트가 발생했었다.

그런데 이 뿐만 아니라 엔터키를 눌렀을 때 같은 이벤트를 발생하고 싶다면..?


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})
}
}


다음과 같다.

만약 input태그의 length가 0 보다 클시(True)에 


@reset이라는 새로운 이벤트가 발생되는 것이다.


FormView.bindEvents = function() {
this.on('submit', e => e.preventDefault())
this.inputEl.addEventListener('keyup', e => this.onKeyup(e))
this.resetEl.addEventListener('click', e => this.onClickReset())
}
FormView.onClickReset = function() {
this.emit('@reset')
this.showResetBtn(false)
}


뿐만 아니고 Clcik 이벤트를 통해서 OnlcikReset이 발생시에

@reset이라는 새로운 이벤트가 발생된다.


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


그리고 @reset이벤트 실행시에  


onResetForm() {
console.log(tag, 'onResetForm()')
this.renderView()
},
renderView() {
console.log(tag, 'rednerView()')
TabView.setActiveTab(this.selectedTab)

if (this.selectedTab === '추천 검색어') {
this.fetchSearchKeyword()
HistoryView.hide()
} else {
this.fetchSearchHistory()
KeywordView.hide()
}

ResultView.hide()
},
fetchSearchKeyword() {
KeywordModel.list().then(data => {
KeywordView.render(data)
})
},
fetchSearchHistory() {
HistoryModel.list().then(data => {
const a = HistoryView.render(data)
a.bindRemoveBtn()
})
},


다음과 같은 것들이 실행된다.(는 재랜더링)


hide()와 show()는 

css display 컨트롤.


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

FormView.bindEvents = function() {
this.on('submit', e => e.preventDefault())
this.inputEl.addEventListener('keyup', e => this.onKeyup(e))
this.resetEl.addEventListener('click', e => this.onClickReset())
}

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})
}

FormView.onClickReset = function() {
this.emit('@reset')
this.showResetBtn(false)
}

FormView.setValue = function (value = '') {
this.inputEl.value = value
this.showResetBtn(this.inputEl.value.length)
}


나머지 폼 태그의 함수에는

showResetBtn()

리셋 버튼의 dispaly 선택


bindEvents()

addEventListener들


onKeyup()

키보드로 작동하는 폼 메소드

1. 입력을 받아 input태그에 내용이 있을시에 reset의 show 여부

2. 엔터시에 submit


onClickReset()

리셋버튼 클릭시 뷰 리렌더링


setValue()

submit을 입력받았을 때 Model과의 동작


TabView.tabNames = {
recommand: '추천 검색어',
recent: '최근 검색어',
}

TabView.setup = function(el) {
this.init(el)
this.bindClick()
return this
}

TabView.setActiveTab = function (tabName) {
Array.from(this.el.children).forEach(li => {
li.className = li.innerHTML === tabName ? 'active' : ''
})
this.show()
}

TabView.bindClick = function() {
Array.from(this.el.children).forEach(li => {
li.addEventListener('click', e => this.onClick(li.innerHTML))
})
}

TabView.onClick = function (tabName) {
this.setActiveTab(tabName)
this.emit('@change', {tabName})
}

export default TabView


setup()

ul태그 저장

bindClick() 실행


setActiveTab()

활성화된 Tab선택

Array.from

Array.from() 메서드는 배열의 형태를 갖거나 순회 가능한 객체로부터 얕게 복사한 Array 객체를 만듭니다.

(https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/from)


모든 url자식 태그에서 innerHTML과 tabName를 비교해서 같다면 className을 변경


bindClick()

모든 li태그에 click이벤트리스너 발생


onClick()

1. setActvieTab을 발생시켜서 활성화된 창을 바꾸기

2. 아래 창 재랜더링 하기 위한 @change 에밋하기