본문으로 건너뛰기
SoulLog

자바스크립트로 서버와 클라이언트 구축하기 — Part 1. Vue.js

12분 읽기

책에서는 Nuxt와 관련된 내용을 중점으로 다루다 보니 Vue.js 자체 내용은 심플한 경향이 있습니다. 실무 경험을 토대로 추가 내용을 보완하였습니다.

1. Vue.js 시작하기, vue 인스턴스 생성

React와 Vue.js

React와 Vue.js를 흔히 자바스크립트 프레임워크라고 하는데, 실제로는 Vue.js는 자바스크립트 프레임워크, React는 자바스크립트 라이브러리라고 지칭합니다.

  • 프레임워크: 이미 정해진 틀에 맞춰서 사용자가 해당 룰에 맞춰서 작업합니다.
  • 라이브러리: 사용자가 필요에 따라 직접 가져다 쓰면서 작업하는 형태입니다.

어느 쪽이 주도권을 쥐고 작업하는지에 차이가 있다고 이해하면 됩니다.

SPA, CSR, SSR

  • SPA (Single Page Application)

    • 페이지를 처음에만 불러오고 이후에는 따로 불러오지 않습니다.
    • 데이터를 수정하거나 조회할 때 페이지를 새로고침하거나 이동하지 않습니다.
  • CSR (Client Side Rendering)

    • HTML 및 Static 파일만 우선적으로 받아오고, 해당 파일들이 로드된 뒤에 자바스크립트 실행을 통해 데이터를 가져옵니다.
    • Vue.js, React에서 사용되는 렌더링 방식입니다.
  • SSR (Server Side Rendering)

    • HTML, Static 파일, 데이터를 전부 한 번에 가져옵니다.

인스턴스 생성

new Vue()로 인스턴스를 생성하며 생성 시 속성 값을 부여할 수 있습니다.

new Vue({
  router,
  store,
  render: h => h(App),
}).$mount('#app')

책에서는 HTML 파일을 토대로 예시를 들다 보니 약간 생성자의 형태가 다릅니다.

let app = new Vue({
  el: '#app',
})

책의 예시에 있는 Vue 스크립트 경로는 현재 Vue 3를 기준으로 잡고 있으므로, Vue 2 사용 시 아래 경로로 바꿔야 합니다.

<script src="https://cdn.jsdelivr.net/npm/vue@2.6.0"></script>


2. 템플릿 문법

텍스트

인스턴스에 정의된 데이터를 {{ }}로 묶어주면 연동됩니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.0"></script>
</head>
<body>
    <div id="app">
        {{ message }}
    </div>
    <script type="text/javascript">
        let app = new Vue({
            el: '#app',
            data() {
                return {
                    message: 'HELLO WORLD'
                }
            },
        })
    </script>
</body>
</html>

추가적으로, Vue 3에서는 없어졌지만 Vue 2에서는 filters를 활용하여 포맷팅 처리도 가능합니다.

<div id="app">
    {{ count | formatNumber }}
</div>
<script type="text/javascript">
    let app = new Vue({
        el: '#app',
        data() {
            return {
                count: 12345,
            }
        },
        filters: {
            formatNumber(value) {
                if(isNaN(value)) {
                    return value
                }
                return value.toLocaleString()
            }
        }
    })
</script>

속성

각각의 element의 속성값에 데이터를 연결할 때는 v-bind:를 사용합니다.

  • 약어를 쓸 경우 v-bind를 제외한 :만 사용합니다.
  • 데이터를 직접 String으로 넣을 경우에는 :을 사용하지 않아도 됩니다.

자바스크립트 표현식

자바스크립트의 객체 또한 직접 활용할 수 있습니다. 하지만 복잡한 javascript 코드 사용을 지양하고 computed를 활용하는 것이 좋습니다.

템플릿 문법이 중요한 점

템플릿을 활용하여 데이터와 바인딩한다는 것은 퍼블리셔와 프론트엔드의 분기로 볼 수 있을 만큼 중요합니다.

  • 프론트엔드: 데이터적인 관점에서 접근 — 활성화된 탭을 데이터로 연동해서 on/off 처리

    changeActiveTabNavi: function (tabName) {
      this.activeTabName = tabName
    },
  • 퍼블리셔: 태그의 속성 관점에서 접근 — element 요소를 찾아 setAttribute로 on/off 제어

    changeActiveTabNavi: function (selector) {
      const tabArr = document.querySelectorAll('.quick-nav a')
      const selectedEl = document.querySelector(`.quick-nav a[href="${selector}"]`)
      const siblingEl = Array.prototype.filter.call(tabArr, (el) => el.getAttribute('href') !== selector)
     
      selectedEl.setAttribute('aria-selected', 'true')
      Array.from(siblingEl).forEach(el => el.setAttribute('aria-selected', 'false'))
    },

3. 데이터 바인딩

단방향과 양방향 바인딩이 모두 가능합니다.

  • 양방향의 대표적인 사례는 v-model입니다.
  • 단, v-model은 한글과 같은 글자 입력에 대한 반응이 느리다는 단점이 있어 별도 처리하는 경우가 많습니다.
  • checkbox는 기본적으로 여러 개 선택을 전제로 하기 때문에 v-model로 처리할 경우 Array 형태로 데이터를 받아야 합니다.
  • 데이터뿐만 아니라 props 또한 단방향과 양방향 바인딩이 모두 가능합니다.

4. computed, watch, methods

computedwatch 모두 데이터의 변화를 감지하여 실행하는 속성입니다.

computed

  • computed의 값은 더 구체적으로 활용할 경우 get()set()으로 역할을 나눠서 처리할 수 있습니다.
numberFormat: {
  get() {
    if (this.value === null) {
      return null
    }
    if (isNaN(Number(this.value))) {
      return ''
    }
    return Number(this.value)
  },
  set(value) {
    if (value === '') {
      this.updateValue(null, 'input')
      return
    }
    this.updateValue(Number(value), 'input')
  },
},

watch

  • 바뀌기 전과 후의 값을 모두 확인할 수 있습니다.
  • 해당 데이터가 변화하면서 다른 데이터에서 이루어져야 할 처리를 하는 경우들이 많습니다.
watch: {
  'product.skus'(value) {
    this.selected.colors = getUniqueItems(value.map(x => x.color.name))
  },
},

5. 조건부 렌더링, 리스트 렌더링

조건부 렌더링

  • 자주 쓰이는 건 v-if, v-else-if, v-else입니다.
  • v-ifv-show 중 바뀌는 편이 많은 것으로 보입니다.
    • v-show로 처리할 경우 display:none 또는 display:block으로 처리되다 보니 다른 display 속성을 썼을 때 이슈가 생기는 경우가 있습니다.
    • $el을 안정적으로 컨트롤해야 할 때 v-show를 선택하는 경우가 많습니다.

리스트 렌더링

  • 반드시 중첩되지 않는 key 값을 사용해야 합니다 (중첩될 경우 에러 발생).
  • 조건부 렌더링과 리스트 렌더링을 함께 사용해서는 안 됩니다 (반드시 별개로 사용).

6. 이벤트 핸들링

click 이벤트

대표적으로 prevent, stop, capture, self가 있습니다.

  • @click.self는 주로 모달 팝업에서 많이 활용합니다.

키보드 입력 이벤트

키보드 입력 이벤트는 대표적으로 keyup, keydown, keypress가 있습니다.

  • keypress의 경우 한글 입력 이벤트를 잘 감지하지 못합니다.
  • Vue에서는 직접 키 값을 연동해서 처리가 가능합니다.
<b-form-input
  ref="addColorInput"
  v-model="newColor"
  :placeholder="`직접 입력 (띄워쓰기 없이 최대 ${newColorNameMaxlength}자 이하로 입력해주세요.)`"
  :maxlength="newColorNameMaxlength"
  class="w-50 m-1"
  autocomplete="off"
  :formatter="formatColorInput"
  @keydown.stop.prevent.enter="addColorOption"
  @keyup.stop.prevent.enter="resetColorInput"
/>

event.preventDefault()와 event.stopPropagation()

  • event.preventDefault(): 태그가 고유로 갖고 있는 이벤트를 발생하지 않도록 처리해주는 역할
  • event.stopPropagation(): 자식 요소의 이벤트가 부모에게까지 전파되지 않도록 하는 역할
  • jQuery에서는 이 두 가지를 한꺼번에 처리하는 방법으로 return false를 넣기도 합니다.
<div class="box">
    <a class="link" href="#" onclick="movePage()">
        <div class="text-area">
            박스 제목 (박스 영역 클릭 시 페이지 이동)
        </div>
        <div class="btn-area">
            <button type="button" onclick="callAlert()">알럿 띄우기</button>
        </div>
    </a>
</div>
<script>
    function movePage() {
        console.log('페이지 이동')
    }
    function callAlert() {
        console.log('알럿 띄우기')
    }
</script>

7. 라이프 사이클

beforeCreate

  • datacomputed에 대한 속성이 생성됩니다.
  • new Vue()에서 지정했던 값들도 사용 가능합니다 (ex. this.$route).
  • props는 아직 생성 전입니다.
// props에 있는 데이터를 호출할 경우 다음과 같은 에러가 발생합니다
// [Vue warn]: Error in beforeCreate hook: "TypeError: Cannot read properties of undefined ..."
 
beforeCreate() {
    console.log('beforeCreate data', this.items)        // undefined
    console.log('beforeCreate computed', this.modalTitle) // undefined
},
created() {
  console.log('created props', this.recentSizeList)   // [__ob__: Observer]
  console.log('created data', this.items)             // [{…}, __ob__: Observer]
  console.log('created computed', this.modalTitle)    // 추가
},

created

  • data, computed, props, method, watch가 모두 세팅되고 데이터도 함께 바인딩된 상태입니다.
  • 아직 $el은 생성 전입니다 (ref로 지정한 내용도 가져오지 못합니다).
  • 대체로 이 단계에서 API 값을 받아와 data에 초기 값을 할당합니다.

beforeMount

  • 데이터가 DOM에 마운팅 되기 전 단계입니다.
  • Server Side Rendering 단계에서는 불러오지 않습니다.

mounted

  • 모든 화면이 렌더링된 상태입니다.
  • 이 단계부터는 $el, $refs 사용이 가능합니다.

beforeUpdate

  • 데이터 변경 후 재 렌더링하기 전 단계입니다.
  • 더보기로 리스트를 추가 로드할 때 스크롤 위치값을 가져오는 용도로 활용한 적이 있습니다.
  • Server Side Rendering 단계에서는 불러오지 않습니다.

updated

  • 데이터가 변경될 경우 재 렌더링 되는 단계입니다.
  • 대체로 UI/UX적으로 새로운 적용이 필요할 때 많이 씁니다.
  • 특정 상태 변경 후 DOM에 액세스해야 하는 경우에는 $nextTick()을 활용합니다.

beforeDestroy

  • Vue 인스턴스가 제거되기 전 단계입니다.
  • Vue3에서는 beforeUnmount로 변경됩니다.
  • Server Side Rendering 단계에서는 불러오지 않습니다.

destroy

  • Vue 인스턴스가 제거된 뒤 호출됩니다.
  • 보통 window.resizewindow.scroll 이벤트를 제거할 때 사용합니다.
  • Vue3에서는 unMounted로 변경됩니다.