[Vue] Vue CLI

작성:    

업데이트:

카테고리:

태그: ,

SFC

Component(컴포넌트)

  • 기본 HTML 엘리먼트를 확장하여 재사용 가능한 코드를 캡슐화 하는데 도움을 줌
  • CS에서는 다시 사용할 수 있는 범용성을 위해 개발된 소프트웨어 구성 요소를 의미
  • 즉, 컴포넌트는 유지보수를 쉽게 만들어 줄 뿐만 아니라, 재사용성의 측면에서도 매우 강력한 기능을 제공


SFC(Single File Component)

  • Vue의 컴포넌트 기반 개발의 핵심 특징
  • 하나의 컴포넌트는 .vue 확장자를 가진 하나의 파일 안에서 작성되는 코드의 결과물
  • 화면의 특정 영역에 대한 HTML, CSS, JS 코드를 하나의 파일(.vue)에서 관리
  • 즉, .vue 확장자를 가진 싱글 파일 컴포넌트를 통해 개발하는 방식

  • Vue 컴포넌트 === Vue 인스턴스 === .vue 파일


단일 파일과 컴포넌트 ⭐

단일 파일에서의 개발

  • 코드의 양이 많아지면 변수 관리가 힘들어지고 유지보수에 많은 비용 발생


여러 컴포넌트

  • 각 기능별로 파일 분할 개발
  • 처음 개발을 준비하는 단계에서 시간 소요 증가
  • 변수 관리가 용이 및 기능별로 유지보수 비용 감소


컴포넌트 구조

한 화면 안에서도 기능별로 각기 다른 컴포넌트 존재

  • 하나의 컴포넌트는 여러 개의 하위 컴포넌트를 가질 수 있음
  • Vue는 컴포넌트 기반의 개발 환경 제공


Vue 컴포넌트는 const app = new Vue({…}) 의 app을 의미하며, 이는 Vue 인스턴스

  • 주의📢 컴포넌트 기반의 개발이 반드시 파일 단위로 구분되어야 하는 것은 아님
  • 단일 .html 파일 안에서도 여러 개의 컴포넌트를 만들어 개발 가능


정리

  • Vue 컴포넌트 === Vue 인스턴스(new Vue({}))
  • Vue 인스턴스 .vue 파일 안에 작성된 코드의 집합
  • HTML, CSS, JS를 .vue 확장자 파일 내에서 관리하며 개발


Vue CLI

  • Vue.js 개발을 위한 표준 도구
  • 프로젝트의 구성을 도와주는 역할
  • Vue 개발 생태계에서 표준 tool 기준을 목표로 함


Node.js

  • JS를 브라우저가 아닌 환경에서도 구동할 수 있도록 하는 JS 런타임 환경
  • Chrome V8 엔진을 제공하여 여러 OS 환경에서 실행할 수 있는 환경 제공
  • 브라우저 밖을 벗어날 수 없었던 JS의 태생적 한계 해결
  • JS를 SSR 아키텍처에서도 사용할 수 있도록 함

  • Ryan Dahl에 의해 발표(2009)


NPM(Node Package Manager)

  • JS를 위한 패키지 관리자
  • pip와 마찬가지로 다양한 의존성 패키지 관리
  • Node.js의 기본 패키지 관리자


설치 과정

vue cli 설치

$ npm install -g @vue/cli
$ vue --version

한 번 설치했다면 또 할 필요는 없다.


vue project 생성

$ vue create my-first-app
Default ([Vue 2] babel, eslint)
  • Vue2 버전을 사용할 것이므로 이를 선택


프로젝트 실행

$ cd my-first-app
$ npm run serve


Babel & Webpack

Babel

  • JavaScript Compiler
  • JS의 ECMAScript 2015+ 코드를 이전 버전으로 번역/변환해 주는 도구
  • 과거 JS의 파편화와 표준화의 영향으로 코드의 스펙트럼이 매우 다양 → 최신 문법이 이전 브라우저 혹은 환경에서 동작하지 않는 상황 발생

  • 원시 코드(최신 버전)를 목적 코드(구 버전)로 옮기는 번역기가 등장 → 개발자는 더 이상 코드가 특정 브라우저에서 동작하지 않는 상황에 대해 크게 고민 X


Webpack

  • Static Module Bundler
  • 모듈 간의 의존성 문제를 해결하기 위한 도구
  • 프로젝트에 필요한 모든 모듈을 매핑하고 내부적으로 종속성 그래프를 빌드


Static Module Bundler

모듈?

단지 파일 하나


배경

  • 브라우저만 조작할 수 있었던 시기의 JS는 모듈 관련 문법 없이 사용
  • JS와 App이 복잡해지고 크기가 커짐 → 전역 scope를 공유하는 형태의 기존 개발 방식의 한계점 발생
  • 라이브러리를 만들어 필요한 모듈을 언제든 호출 / 코드를 모듈 단위로 작성하는 등의 다양한 시도


여러 모듈 시스템 ⭐

  • ESM(ECMA Script Module)
  • AMD(Asynchronous Module Definition)
  • CommonJS
  • UMD(Universal Module Definition)


Module 의존성 문제

  • 모듈의 수가 많아지고 라이브러리 혹은 모듈 간의 의존성(연결성)이 깊어짐 → 특정한 곳에서 발생한 문제가 어떤 모듈 간의 문제인지 파악하기 어려움

  • 즉, Webpack은 이 모듈 간의 의존성 문제 해결을 위해 등장


Bundler

  • Bundling : 모듈 의존성 문제를 해결하는 작업
  • Bundler : Bundling을 해주는 도구
  • Webpack : Bundler 중 하나

  • 여러 모듈을 하나로 묶어주고 묶인 파일은 하나(or 여러 개)로 합쳐짐
  • Bundling된 결과물은 더 이상 순서에 영향을 받지 않고 동작
  • snowpack, parcel, rollup.js 등의 다양한 모듈 번들러 존재

  • Vue CLI는 이러한 Babel, Webpack에 대한 초기 설정이 자동


정리

Node.js

  • JS Runtime Environment
  • JS를 브라우저 밖에서 실행할 수 있는 새로운 환경


Babel

  • Compiler
  • ES2015+ JS 코드를 구버전의 JS로 바꿔주는 도구


Webpack

  • Module Bundler
  • 모듈 간의 의존성 문제를 해결하기 위한 도구


Vue 프로젝트 구조

node_modules

  • node.js 환경의 여러 의존성 모듈


public/index.html

  • Vue 앱의 뼈대가 되는 파일
  • 실제 제공되는 단일 html 파일


src

  • assets : webpack에 의해 빌드된 정적파일
  • App.vue : 최상위 컴포넌트
  • components : App.vue 내부에 들어가는 하위 컴포넌트 디렉토리

  • main.js
    • webpack이 빌드를 시작할 때 가장 먼저 불러오는 entry point
    • 실제 단일 파일에서 DOM과 data를 연결했던 것과 동일한 작업이 이루어지는 공간
    • Vue 전역에서 활용할 모듈을 등록할 수 있는 파일


babel.config.js

  • babel 관련 설정이 작성된 파일


package.json

  • 프로젝트의 종속성 목록과 지원되는 브라우저에 대한 구성 옵션 포함


package-lock.json

  • node_modules에 설치되는 모듈과 관련된 모든 의존성 설정 & 관리
  • 팀원 및 배포 환경에서 정확히 동일한 종속성을 설치하도록 보장
  • 사용할 패키지의 버전 고정
  • 개발 과정 간의 의존성 패키지 충돌 방지


Pass Props & Emit Events

컴포넌트 작성

  • Vue app은 자연스럽게 중첩된 컴포넌트 트리로 구성
  • 컴포넌트간 부모-자식 관계 구성, 이들 사이의 의사소통 필요


props는 아래로 events는 위로

  • 부모는 자식에게 데이터 전달(pass props)
  • 자식은 자신에게 일어난 일을 부모에게 알림(emit event)
  • 부모와 자식이 명확하게 정의된 인터페이스를 통해 격리된 상태 유지 가능


props

  • 부모(상위) 컴포넌트의 정보를 전달하기 위한 사용자 지정 특성
  • 자식(하위) 컴포넌트는 props 옵션을 사용하여 수신하는 props를 명시적으로 선언
  • 주의📢 자식 컴포넌트의 템플릿에서 상위 데이터를 직접 참조할 수 없음


가. Static Props 작성법

  • 자식 컴포넌트에 보낼 prop 데이터 선언
// App.vue
<template>
  <div id="app">
    <app-child my-message="This is prop data"></app-child>
  </div>
</template>


  • 부모 데이터로부터 받은 props 데이터를 명시적으로 선언
  • 이를 template에서 사용
// AppChild.vue
<template>
  <div>
    <h1>Child</h1>
    <h2>{{ myMessage }}</h2>
  </div>
</template>

<script>
export default {
  name: "AppChild",
  props: {
    myMessage: String,
  }
}
  • 자식 컴포넌트로 보낼 때, 즉 template에서는 kebab-case로 사용 및 전달
  • 자식 컴포넌트에서 받을 때, 즉 script에서는 camelCase로 선언(declaration)
  • 데이터 타입을 정의해준다.(String, Array)
  • 중괄호로 열어 type: String, required:true 등 여러 옵션을 둘 수 있다.


나. Dynamic Props 작성법

  • v-bind directive를 사용
  • 부모컴포넌트의 data의 props를 동적으로 바인딩
  • 부모에서 데이터가 업데이트 될 때마다 자식 데이터로도 전달
// App.vue
<template>
  <div id="app">
    <app-child my-message="This is prop data"></app-child>
    <app-child :parent-data="parentData"></app-child>
  </div>
</template>

<script>
export default {
  name: "App",
  components: {
    AppChild,
  },
  data: function () {
    return {
      parentData: 'This is parent Data'
    }
  }
}
  • 값으로 넣은 parentData가 단순 문자열이 아닌, data 내의 변수(상수)임을 의미
  • 이를 binding하는 것


data가 함수?

  • 컴포넌트의 data는 반드시 함수여야 한다.
  • 기본적으로 각 인스턴스는 모두 같은 data 객체를 공유하므로 새로운 data 객체를 반환(return)해야 함
  • 인스턴스 각각마다 처음엔 같은 값을 return해 시작하지만 각각 조작을 달리 할 수 있으므로 개별성을 위해 함수형
data: function () {
  return {
    message: null,
  }
}


props 전달과 v-bind

  • props 전달에서 자주 실수하는 부분
  • v-bind 없이 static 구문을 사용해 숫자 전달
  • 실제 JS 숫자를 전달하려면 값이 JS 표현식으로 평가되기 위해 v-bind 사용
// 1. 일반 문자열 "1" 전달
<comp some-prop="1"></comp>

// 2. 실제 숫자 1 전달
<comp :some-prop="1"></comp>


단방향 데이터 흐름

  • 모든 props는 하위 속성과 상위 속성 사이의 단방향 바인딩 형성
  • 부모 속성이 변경되면 자식 속성에게 전달, 반대 방향은 안 됨
  • 자식 요소가 의도치 않게 부모 요소의 상태를 변경하여 앱의 데이터 흐름을 망치는 일을 방지
  • 부모 컴포넌트가 업데이트될 때마다 자식 요소의 모든 prop들이 최신값으로 업데이트


Emit event

  • Listening to Child Components Events


$emit(eventName)

  • 현재 인스턴스에서 이벤트를 트리거
  • 추가 인자는 리스너의 콜백 함수로 전달


부모자식 컴포넌트 간 데이터 전달 과정

  • template의 자식 컴포넌트 부분에 v-on을 사용하여 자식 컴포넌트가 보낸 이벤트 청취


// AppChild.vue
<template>
  ...
  <input
    type="text"
    @keyup.enter="childInputChange"
    v-model="childInputData"
  >
</template>
  • v-model을 이용해 childInputData와 양방향 갱신
  • enter key가 눌리면 childInputChange 콜백함수 실행


// AppChild.vue
methods: {
  childInputChange: function () {
    this.$emit('child-input-change', this.childInputData)
  }
}
  • child-input-change 이벤트를 트리거
  • childInputData를 전달


// App.vue
<template>
  ...
  <app-child
    ...
    @child-input-change="parentGetChange"
  >
</template>
  • 부모 컴포넌트인 App.vue에서 app-child 컴포넌트에서 이벤트 청취
  • $emit을 통해 올려보냈던 ‘child-input-change’ event에 대해 청취하면 ‘parentGetChange’ 콜백함수 실행


methods: {
  parentGetChange: function (inputData) {
    console.log(`AppChild로부터 ${inputData}를 받음`)
  }
}
  • 인자로 전달한 childInputData를 임의의 변수명으로 함수 내에 전달해 사용


event 이름 컨벤션

  • 컴포넌트 및 props와 달리, 이벤트는 자동 대소문자 변환 제공 X
  • HTML의 대소문자 구분을 위해 DOM 템플릿의 v-on 이벤트리스너는 항상 소문자로 변환
  • 즉, template 내에서 ‘v-on:myEvent’는 ‘v-on:myevent’로 자동변환
  • 때문에 이벤트 이름으로는 항상 kebab-case를 사용하는 것을 권장
this.$emit('myEvent') // 오답
this.$emit('my-event') // 정답
<app-child @my-event="callbackFunc"></app-child>

댓글남기기