Vue.js 학습 #1

하루히즘·2021년 11월 27일
1

Vue.js 학습 기록

목록 보기
1/3

이전 포스트에서 SimpleTodoList 같은 API 애플리케이션만 만들다보니 정작 화면 설계가 안되서 어떤 식으로 API를 개발해야 할 지 잘 모르겠다고 언급했었다. 그리고 이번에 OAuth 기능까지 적용하면서 리디렉션 때문에 정말 절실하게 프론트엔드 페이지가 필요하겠다고 생각하게 됐다.

그래서 프론트엔드 3대 프레임워크(리액트, 앵귤러, 뷰) 중 Vue.js를 학습해서 내가 만든 API를 나타낼 수 있는 화면은 스스로 만들어보자고 결심하게 되었다. 굳이 Vue.js를 선택한 이유는 특별히 없고 맨 처음 접했던(정확히는 귀동냥으로 들었던) 프레임워크기 때문이다.

학습 방향은 newline 이라는 업체에서 제공하는 30 Days of Vue라는 전자책과 Vue.js 가이드를 참조하며 진행할 생각이다.

학습 내용

Vue.js란?

오픈소스 프론트엔드 자바스크립트 프레임워크. Approachable, versatile, performant 세 가지를 강점으로 내세우고 있다.

  • Approachable: HTML, CSS, JS 만 알고 있으면 언제든 접할 수 있다.
  • versatile: vue-cli, vue-router 등 관련 컴포넌트를 다양하게 제공하여 프레임워크의 역할을 다함.
  • performant: 가상 DOM을 사용하여 재 렌더링(re-render) 시간을 줄이고 vue 자체도 최적화가 잘 되어있음.

주로 사용자 인터페이스(UI)를 만들기 위한 점진적인(progressive) 프레임워크인데 처음에는 뷰 레이어를 중점으로 개발하고 필요하면 추가적인 모듈을 붙여나갈 수 있다는 점을 강조하는 것 같다.

초기 설정

CDN에서 가져오거나 로컬에서 스크립트로 참조한 후 Vue 클래스의 객체를 생성하면 된다. 이 객체를 생성할 때는 el 이란 값을 넘기는데 이 값이 element, 즉 Vue.js 애플리케이션이 연결할(mounting-point) HTML Element를 지칭한다.

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

즉 위처럼 el#app, 즉 'app'이란 id의 HTML element를 설정하면 다음처럼 <body> 내부에 'app'이란 id를 가지는 <div> 를 생성해서 사용할 수 있다.

<html>
  <body>
    <div id="app">
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script src="./main.js"></script>
  </body>
</html>

그래서 이 element 내부에 있는 요소들을 vue의 인스턴스에서 관리할 수 있다. Vue.js를 공부하면서 제일 처음 겪었던 실수가 이 'app' 엘리먼트 밖에서 뷰를 사용하려고 했던 것이기 때문에 앞으로 신경써야 할 부분이다.

선언적 렌더링

인상깊었던 점은 Vue에서 Mustache의 문법을 활용하고 있다는 점이다. 스프링 프로젝트를 진행할 때 Thymeleaf나 Mustache 같은 SSR 템플릿 엔진을 알게 됐는데 {{ data }} 처럼 표현하는 Mustache 문법을 이 Vue의 DOM에서도 사용할 수 있다.

<div id="app">
  {{ data }}
</div>

그러면 이제 div#app에는 data를 출력하는 템플릿이 등록됐다. 하지만 단순히 서버측에서 렌더링하던 것처럼 문자열을 채워주는 템플릿 렌더링과는 좀 다른 점이 있다.

Vue 인스턴스

Vue 객체를 생성하면 Vue 인스턴스가 생성된다. 이 인스턴스가 프론트엔드 애플리케이션의 엔트리 포인트가 되며 가장 큰 특징은 인스턴스에서 사용할 데이터나 메서드를 관리한다는 것이다.

new Vue({
  el: "#app",
  data: {
    greeting: "Cruel World!",
    name: "James",
    age: 26,
  },
  methods: {
    clickChangeButton: function () {
      this.greeting = "Hello World!";
    },
  },
});

위처럼 Vue 객체의 생성자의 data 항목에 greeting, name, age 등의 데이터를 설정해 줄 수 있는데 이 데이터들은 reactive하게 접근할 수 있다. 이게 무슨 말이냐면 데이터가 변경되면 이에 반응하여 아래처럼 데이터를 참조하는 HTML element 자체도 다시 갱신된다는 것이다.

  <div id="app">
    <h2>{{ greeting }}</h2>
    <p>by {{ name }} ages at {{ age }}</p>
  </div>

위의 코드에서는 Vue 인스턴스의 생성자에 따라 "Cruel World!", "by James ages at 26" 문자열이 렌더링된다. 그런데 나중에 Vue 인스턴스의 greeting이나 name, age 데이터가 변경된다면 이 문자열 역시 그에 따라 자동으로 변경된다.

Vue 인스턴스에서는 초기에 주입받은 greeting, name 같은 데이터에 대한 접근자(getter/setter)를 만들어서 반응형 시스템을 통해 다룰 수 있도록 지원한다. 이런 부분은 클라이언트의 개입 없이(under-the-hood) 이루어진다.

원래는 this.$data.greeting 처럼 접근해야 하는데 프록시 기법으로 this.greeting 처럼 간단하게 접근할 수 있다고 한다. greeting 이란 항목에 접근하면 프록시 객체가 getGreeting 같은 메서드라고 찾는 건가? 이것 역시 좀 더 알아볼 것.

이 외에도 Vue 인스턴스의 속성에 직접적으로 접근할 수 있도록 '$' 문자로 필드에 접근(i.e. vueInstance.$data, vueInstance.$el)하거나 메서드(i.e. vueInstance.$watch('el', function(newVal, oldVal) { ... }))를 호출할 수 있다. 나중에 관련 API 문서를 읽고 적용할만한 기능이 있을 지 알아보자.

인스턴스 라이프사이클

Vue 인스턴스 역시 스프링의 Bean 객체처럼 라이프사이클을 갖고 있으며 그 사이클에 간섭할 수 있는 훅을 제공한다.

위의 라이프사이클에서 해당하는 부분에 인스턴스 생성 시 함수를 대입하면 된다.

new Vue({
  data: {
    a: 1
  },
  created: function () {
    // `this` 는 vm 인스턴스를 가리킵니다.
    console.log('a is: ' + this.a)
  }
}) // "a is: 1"

인스턴스 생성, 파괴 뿐 아니라 반응형 시스템이 동작할 때도 간섭할 수 있으니 나중에 활용할 부분이 있을 것이다.

지시어

이벤트 핸들러를 HTML Element에 등록하거나 특정 속성을 데이터에 바인딩하기 위해 v-on:click, v-bind:title 같은 지시어(directive)를 설정할 수 있다.

<button v-on:click="clickChangeButton">Button to change</button>
<span v-bind:title="message">Bound to message!</span>

예를 들어 v-on 시리즈는 이벤트 핸들러를 등록하고 v-bind:titletitle 속성을 message 데이터의 최신값으로 연결한다는 것이다. 그래서 버튼을 클릭하면 clickChangeButton 메서드가 호출될 것이며 message가 변경되면 title 속성 역시 reactive 하게 변경될 것이다.

이 외에도 분기를 위한 v-if, 반복을 위한 v-for, 양방향 연결(입력폼 등)을 위한 v-model 등 'v-'로 시작하는 다양한 지시어를 제공한다. 특히 반복문같은 경우 반복문에서 참조하는 자료구조에 데이터가 추가된다면 추가된 데이터도 역시 reactive 하게 처리된다.

컴포넌트 재사용

Vue의 컴포넌트(component)는 미리 정의된 속성을 가진 재사용 가능한 Vue 인스턴스다. Vue 클래스의 정적 component 메서드로 등록할 수 있다.

Vue.component("todo-item", {
  props: ["item"],
  template: "<div><span>ITEM: {{ item.text }}</span></div>",
});

속성으로 해당 컴포넌트가 그릴 HTML 템플릿(template)을 지정하거나 외부 컴포넌트에서 받을 데이터(props)를 지정할 수 있다. 이 데이터는 이 컴포넌트를 생성할 때 위에서 사용한 v-bind 지시어를 통해 item 이라는 필드로 전달받을 수 있다.

<div id="todolist">
  <todo-item v-for="item in items" v-bind:item="item" v-bind:key="item.id"></todo-item>
</div>

그러면 템플릿에서는 이 item 데이터의 text 필드를 참조하여 렌더링할 수 있는 것이다. 마치 클래스 생성자에 파라미터를 전달해주는 것처럼 생각하면 될 것이다.

컴포넌트의 좋은 점은 애플리케이션을 하나의 거대한 HTML 소스 코드로 묶지 않고 기능별로 분리할 수 있다는 것이다. 당장 위의 예시처럼 할 일 리스트를 출력하는 <div>를 따로 분리해서 구현한다던지 추가적인 컴포넌트(캘린더, 사이드바 등)를 구현해서 얼마든지 적용할 수 있다.

컴포넌트가 인스턴스라는 것은 각 컴포넌트가 별도의 상태를 갖고 있다는 것을 의미한다. 예를 들어 내부적으로 숫자를 세는 카운터 기능이 있을 때 해당 컴포넌트를 여러개 배치하면 서로 고유한 카운터를 사용하게 된다.

Data Driven Architecture

이처럼 데이터의 변경이 reactive 하게 자동으로 반영되기 때문에 뷰 애플리케이션을 Data-Driven Architecture 처럼 다룰 수 있다. 즉 데이터의 변경이 실제 화면의 변화를 이끌어낸다는 것이다.

만약에 뷰를 사용하지 않고 직접 이를 구현하려면 어떻게 해야 했을까? 예를 들어 버튼을 눌렀을 때 greeting 문자열을 변경시키거나 age 를 증가시키는 기능을 구현하려고 했다면 아래처럼 document 객체의 getElementById나 기타 탐색 메서드를 사용해서 HTML element 자체를 얻어와야 할 것이다.

let greetingTag = document.getElementsByTagName("h2")[0];
changeGreeting = () => {
  if (greetingTag.textContent === 'Hello World!') {
    greetingTag.textContent = 'What is up!';
  } else {
    greetingTag.textContent = 'Hello World!';
  }
}

게다가 데이터가 변경되었을 때 여러 element에 동시에 반영하려면 각각 이벤트 핸들러를 모두 호출해야 할 것이며 동적으로 요소가 추가되거나 삭제된다면 이를 따로 기억해야 하는 등 번거로운 작업이 많아진다.

이는 기본적으로 DOM을 단일 신뢰 출처(single source of truth)로 사용하기 때문이다. 하지만 Vue 인스턴스를 사용하면 이 인스턴스에서도 데이터를 관리할 수 있기 때문에 위처럼 DOM 트리를 훑는 번거로운 작업이 필요하지 않다.

대신 인스턴스 생성 시 데이터를 생성해서 필요한 곳에 주입하기 때문에 사용하려는 인스턴스의 데이터는 생성 시 모두 전달해야 하며 인스턴스가 생성된 후 추가하거나 삭제할 수 없다. 그래서 빈 값이라도 초기값을 줘서 등록하거나 Vue 3.0에서는 생성 후에도 추가, 삭제가 가능하다고 하는데 좀 더 알아볼 것.

후기

중구난방으로 정리해서 난잡하지만 오늘이 미루고 미루던 학습을 시작한 첫날이기 때문에 이에 의의를 둔다.

profile
YUKI.N > READY?

2개의 댓글

comment-user-thumbnail
2021년 11월 27일

저도 이분이 만드신 api 써보고싶어요

1개의 답글