Vue.js
JS 프레임워크 백년전쟁에 결론이 날 때가 되었다라고 하는데 사실 모르겠다
HELLO WORLD 로 슥 훑어보는 삼대천왕
Q: <div>Hello from ${framework}</div>
찍기
react
(사실 안써봐서 카더라 통신임)
<!DOCTYPE html>
<html>
<head>
<title>React Hello World</title>
<script src="https://fb.me/react-15.0.0.js"></script>
<script src="https://fb.me/react-dom-15.0.0.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.min.js"></script>
</head>
<body>
<div id="greeting"></div>
<script type="text/babel">
var Greeting = React.createClass({
render: function() {
return (
<p>Hello from React</p> // p 태그 필요한건가...? 이것조차 모름. 날 믿지 말아여...
)
}
});
ReactDOM.render(
<Greeting/>,
document.getElementById('greeting')
);
</script>
</body>
</html>
. | react |
---|---|
origin | 페북 |
good | 구조 단순, 유저수 많음 = 물어볼 데 많음 = 써드파티 많음 = 신기능 빨리나옴 |
bad | 코어가 단순한 만큼 추가 솔루션이 많이 필요하고 또 많이 제공 되어 있다지만 검증되지 않은 써드파티가 많음 = lib 구성할때 더듬더듬 |
ang 투쁠
물론 요즘같은 세상에 cdn 파일 끌어다가 쓰겠단 건 아니지만 한 파일로 표현조차 못하겠음 + 컴파일까지 해야 돌아감
// app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
<!-- index.html -->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Angular Hello World</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>
<!-- app.component.html -->
<div style="text-align:center">
Hello from {{what}}
</div>
// app.components.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
// styleUrls: ['./app.component.css'] // css가 필요할 경우 이런식으로.
})
export class AppComponent {
what = 'Angular';
}
. | ang 2+ |
---|---|
origin | 구글 |
good | 손에 익으면 생산성은 좋음, 머테리얼 디자인 적용 쉬움 |
bad | ts + RXJS 까지 사실상 같이 봐야 함 + 지들만의 세상을 한참 공부하도록 시킴 손에 익기까지 오래걸리고 파일 갯수가 무한 증식(컴포넌트*3)하며 뭐 선언만 하나 잘못되어도 콘솔에 에러로그가 100줄정도씩 떠서 디버깅이 무슨 암호해독처럼 느껴짐 = 유지보수는 내가 안 하겠다는 굳은 마음을 먹게 됨 장점만 발라내어 레거시에 붙이기 불가능 = 프레임워크 적용하려고 하면 mkdir 부터 시작해야함 |
vue.js
<!DOCTYPE html>
<html>
<head>
<title>Vue Hello World</title>
<script src="https://vuejs.org/js/vue.min.js"></script>
</head>
<body>
<div id="app">
{{ message }}
</div>
<script>
new Vue({
el: '#app',
data: {
message: 'Hello from Vue'
}
});
</script>
</body>
</html>
. | vue.js |
---|---|
origin | 대륙의 개발자 |
good | 단순. 검증 완료된 추가기능 솔루션들. 프레임워크의 적용 범위를 개발자가 결정 가능. 문서 짱. |
bad | 리액트에 비하면 사용자 쫌 적은거 같다 ? |
risk | (사실상) 1인 개발 체제 - 댓글 알바라도 풀었는지 이런 의견 종종 보이던데 이게 모 큰 흠인가 싶긴 함 |
요약: 코드가 한 눈에 이해되고 구조가 단순하면서도 점진적 적용이 가능하다는 뷰를 써볼까요 ?
준비물: 설치 + boilerplate CLI
vue.js의 Getting started를 읽고 주절주절
다른 단일형 프레임워크와 달리 Vue는 점진적으로 채택할 수 있도록 설계하였습니다.
뷰의 코어는 발음 그대로 뷰에만 관여 -> 다른 라이브러리 생각 않고 레거시에 붙이기 가능(포부 소박하고 쿨해…)
‘누군가 알아서 나를 알아봐주겠지’하지 않고 매력발산 열심히 함
공식 라이브러리도 잘 준비해 둠
그리고 이 모든것보다 우선해서, 가이드 문서가 쩔게 잘 되어 있다.
vue 특징들 : REACTIVE, DIRECTIVE, COMPONENT-IVE
<div id="app">
<span v-bind:title="message">
span.title attr에 app.message를 'keep' 바인딩
</span>
</div>
var app = new Vue({
el: '#app',
data: {
message: 'Hi Again!'
}
})
1. ‘선언적’ 렌더링 + REACTIVE(데이터 -> DOM)
선언형(declarative)은 명령형(imperative)이라는 말과 대치되는 단어.
우리가 기존에 message
를 어떤 화면에 찍기 위해서는 이런 파편회된 조치들이 필요했다.
- 콘솔표시 -> console.log()
- 화면표시 -> innerHTML=””
- 출력 -> …
특정 언어의 초기에는 이러한 조치들을 구구절절맨으로 개발자가 하나하나 처리해야 한다면(imperative)
일정 기간 그 언어에 대한 여러가지 폴백과 고민들이 이루어지고 나면
그 처리를 위임하는 시스템을 구축할 수 있고 그게 ‘선언적’ 인 제스쳐로 나타난다.
그 예로, 선행된 ‘선언’의 역사에는 이런 것들이 있다.
- 마크업 언어
- css -
border-radius
‘반응형’이라는 의미도 이와 비슷한 맥락의 이야기인데,
뷰의 경우 바인딩된 모델의 데이터가 변경되는 경우 뷰의 값도 ‘계속’ 변경된다.
이것도 예전에 이런식으로 구구절절 명령해 줘야 했다면
- 이벤트 탐지
- 이전의 값과 같은가? + validation
- 업데이트
- 실패하면
반응형은 마치 like excel 처럼, 그 중간 과정들을 생략하고
원본 데이터 변경 = 결과도 변경
이런 흐름으로 app을 개발할 수 있게 만들어 준다.
2. v-
DIRECTIVE : 렌더링된 돔에 특수한 반응형 동작을 지시함
v-bind:(prop)
v-if=(조건구문)
,v-for="(반복구문)"
: 텍스트, 속성 뿐 아니라 DOM 구조에도 데이터 바인딩 가능v-on:(eventName)
: 이벤트 리스너 선언 가능v-model="(prop)"
: 양방향 바인딩 가능
3. COMPONENT
1.데이터: 이런 데이터가 들어있는 뷰 인스턴스를
var app = new Vue({
el: '#app',
data: {
groceryList: [ // 이것을 사용할 예정
{ id: 0, text: 'Vegetables' },
{ id: 1, text: 'Cheese' },
{ id: 2, text: 'Whatever else humans are supposed to eat' }
]
}
})
2.템플릿: 이렇게 정의된 컴포넌트를 이용해서
Vue.component('todo-item', {
props: ['todo'], // todo 변수로 부모님이 데이터 주실거야
template: '<li>{{ todo.text }}</li>' // 그럼 <todo-item></todo-item>을 이걸로 치환해
})
props
option- vue component는 독립된 스코프를 가지게 되는데
- 이 때 부모의 데이터를 자식 컴포넌트에게 전달해 주는 방법
3.사용: 요렇게 렌더링 할 수 있다.
<div id="app">
<ol>
<todo-item
v-for="item in groceryList"
v-bind:todo="item"> <!-- item 데이터를 todo 라는 키로 전달 -->
</todo-item>
</ol>
</div>
설명을 위해 순서를 바꿨지만 실제 코딩은 이것의 거의 역순으로. (본문 예제보기)
여기부터 Essentials
공식문서와 이곳을 뛰어다니니 어지러움 주의
vue 인스턴스에 관하여
일단 이렇게 인스턴스를 만듦. 뷰 인스턴스는 app 내에 일반적으로 하나(=루트인스턴스)가 존재.
var vm = new Vue({
// 데이터, 템플릿, 마운트할 엘리먼트, 메소드, 라이프사이클 콜백 등의 옵션 props
})
- 뷰 컴포넌트도 결굴 뷰 인스턴스라는 것만 알고 넘어가자 (물론 루트 인스턴스 특화 옵션이 있긴 함)
- 컴포넌트들을 사용하다보면 이런 트리구조 어플리케이션을 구성할 수 있음.
Root Instance └─ TodoList ├─ TodoItem │ ├─ DeleteTodoButton │ └─ EditTodoButton └─ TodoListFooter ├─ ClearTodosButton └─ TodoListStatistics
- 컴포넌트 시스템 나중에 또 얘기할거니까 넘어가 넘어가
constructor option: data
- 인스턴스 생성시
data
옵션 - 오브젝트 내에 미리 정의된 prop만 반응형으로 동작- (아마)
Proxy
+Object.defineProperty
를 이용해서 객체의 변경을 탐지하는 듯 한데defineProperty
는 prop의 ‘선언’이나 ‘변경’만을 감지 할 수 있음.
(‘추가’ 는 감지가 안 됨, prop의 reference가 선언시에 참조되어야 하는 듯)
- (아마)
- 그러니 나중에 필요한 prop은 초기값으로 미리 넣어서 참조할 수 있도록 사용해야 함.
- 혹시 데이터 바인딩을 통째로 막아야 하는 상황에 대비해서
Object.freeze()
제공
(혹-시 vue.js 구현 궁금하면 열어서 Object.defineProperty
같은 것을 찾아보자.)
instance methods: $
prefix로 제공
vm.a = vm.$data.a
이런 alias 정도임
잘 안씀 = 안 중요함 = 지나가 지나가
constructor options: instance lifecycle hooks
(이벤트가 아닌) 콜백으로 특정 타이밍에 추가동작을 넣을 수 있음. (그림의 빨간 박스들)
beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
activated
deactivated
beforeDestroy
destroyed
errorCaptured
템플릿 문법
Vue는 템플릿을 virtual DOM 렌더링 함수를 이용해서 컴파일 함.
= 모델과 화면의 중간에 가상화 레이어(virtual DOM)를 두고 실제 화면의 재 렌더링을 최소화
- 내부의 반응형 시스템을 이용하여 상태 변경을 추적하고
그 데이터를 기반으로 virtual DOM DIFF를 이용하여 최소한의 rendered DOM을 조작하도록 동작함.
문법들
- 문자열 및 표현식 = { { mustache } }
- 디렉티브
v-${name}="단일 JS 표현식"
v-${name}:${args}
v-${name}:${args}.${modifiers}
- shorthand
v-bind:
->:
v-on:
->@
vue의 Watch 기능
constructor option: watch
data
옵션의 특정 프로퍼티 감시 -> 뭔가를 변경할 때 프록시 기능
= 기타 다른 프레임워크에서 제공하는 감시 기능과 유사
vm.$watch
와 동일- 어떨 때 유용한가 : 데이터 바인딩의 흐름 사이에서 추가 작업이 필요한 경우 or 의도적으로 흐름에 딜레이를 주려는 경우
- 다른 라이브러리(데이터 변경 시점에 트리거가 필요한)와 혼용하는 경우
- debounce
- 양방향바인딩 같은 데이터는 걸어둘만 함
constructor options: computed
, methods
vue: watch
제공하긴 하는데 웬만한건 알아서 해 드릴게여~
그런데 data
options에 있는 원본 값들을 가지고
템플릿에서 표현식으로 구구절절 값을 변경할 수 있긴 하지만 지저분하지요 ?
-> 템플릿에 로직 넣지 말고 computed
옵션을 쓰세여
computed
는 의존성 변경이 없는한 캐시됨- message 변수 값을 뒤집은 reversedMessage(computed값)의 경우, message 값이 변하기 전까지 캐싱
- 캐싱이 가능한 이유:
computed
객체에 getter 정의 시this.message
(=원본데이터)의 참조를 저장, 원본 변경을 감지함
혹시 캐싱을 원하지 않는 경우, 재 렌더링 타임 마다 호출되는 methods
쓰고 표현식에서 호출하세여.
v-bind
: 클래스, 스타일 디렉티브
이거 많이 쓸텐데… 문자열 처리를 각각 해주려면 귀찮겠죠 ? 매-직 제공해 드립니다.
클래스 바인딩
- 객체 지원:
:class="{active: isActive}"
- 바인딩 된 객체는 위처럼 인라인일 수도,
data
prop에 정의된 객체일수도,computed
객체일수도.
- 바인딩 된 객체는 위처럼 인라인일 수도,
- 배열 지원:
:class="[activeClass, errorClass]"
- 혼용 지원:
:class="[{active: isActive}, errorClass]"
= 삼항연산
이 문법은 컴포넌트에도 동일하게 적용.
된다고 하는데 컴포넌트 아는게 없어서 설명을 못하고 넘어감…
스타일 바인딩
- 객체 지원:
:style="{fontSize: fontSize + 'px'}"
- 속성 이름에 camelCase, kebab-case 둘 다 사용 가능
- 배열 지원:
:style="[baseStyles, overridingStyles]"
- 벤더 프리픽스 자동 (이라는데 재현을 못해봄)
- 추가 loader를 써야 하나 ?
- 폴백 시스템 제공:
:style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"
Q: 컴파일 시점에 브라우저 정보를 알고 있진 않을텐데, 그럼 vue는 런타임에 이것저것 챙겨 보고 업데이트를 시키는건가 ?
A: 아마도 ?
조건부 렌더링
vue는 조건에 안 맞으면 아예 rendered DOM에 그리지를 않는다.
(<button disable=false></button>
를 <button></button>
으로 깨끗하게 렌더링 하는 것 처럼, 굳이 필요없는 attr이나 노드는 아예 제거하는 정책)
예제를 보면 이해하기 쉬움: 개발자 도구 열어놓고 돔 변경되는 것 확인하기.
v-if
v-else
v-else-if
<template>
: 렌더링 토글 대상그룹으로 사용할 수 있는 문법- 가능한 한 렌더링을 줄이려고 하기 때문에 토글그룹끼리 겹치는 요소들이 다시 렌더링 되지 않는 문제가 생길 수 있음 -
key
attr 사용
- 가능한 한 렌더링을 줄이려고 하기 때문에 토글그룹끼리 겹치는 요소들이 다시 렌더링 되지 않는 문제가 생길 수 있음 -
- 조건에 안 맞으면 아예 그리지 않음
- 런타임에 이 조건이 변경되면 마크업에서 실제 노드를 조작(비용↑)
v-show
- 마크업에는 노드가 항상 존재하고
display:none
추가/제거
- 마크업에는 노드가 항상 존재하고
리스트 렌더링
v-for
v-for="itm in array"
v-for="(itm, idx) in array"
v-for="(val, key, idx) in obj"
- 내부적으로Object.keys()
사용in
=of
v-bind:key
- 가능한 한 렌더링을 줄이려는 정책이기 때문에 DOM을 재사용하다보니 업데이트시 예상하지 않는 동작을 할 수 있듬
- 위의 조건부 렌더링에서
key
attr 처럼 다시 렌더링(리스트의 경우엔 재정렬)해야 한다는 힌트를 제공해야 함. key=고유한 id를 사용하는 것이 일반적. - 이 구문은 vue가 노드를 식별하는 방법이기 때문에
v-for
와 상관없는 다른 노드에서도 사용가능.
v-for="itm in 10"
- 이때
<template>
: 반복 대상그룹으로 사용할 수 있는 문법.
priority: v-for > v-if
== for 구문을 돌면서 if 조건을 체크해서 렌더링 함.
<li v-for="todo in todos" v-if="!todo.isCompleted">
{{ todo }}
</li>
리스트용 데이터들 바인딩 - 이런 구멍 피하세요
배열
- 배열 데이터를 래핑하여 추적 가능하게 제공하는데요
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
- 래핑되지 않은 방법으로 수정하면 감지 모태…
- 래핑되지 않은 방법 == 배열에 인덱스로 접근해서 수정하거나
length
를 변경하는 등 Vue.set
이나splice
를 활용하세요.
- 래핑되지 않은 방법 == 배열에 인덱스로 접근해서 수정하거나
- non-mutating 하고 싶다면 -
filter()
concat()
slice()
사용- 데이터 레퍼런스 변경됐다고 기존 DOM을 다 버리고 렌더링 하지는 않으니 걱정ㄴㄴ
객체
- vue instance 생성 시점에 선언이 안 되어 있었다면 바인딩도 안 되는 한계는 이렇게 해결할 수 있습니다.
Vue.set(obj, key, val)
으로 동적 추가 가능합니다.- ==
vm.$set
- ==
- 혹은 기존의 obj를 확장해서 아래처럼 재할당 하시면 되여.
this.userProfile = Object.assign({}, this.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
v-for
와 컴포넌트 같이 쓸 수 있는데요
이 때는 컴포넌트의 독립된 스코프를 보장하기 위해, 데이터를 자동으로 주입하지 않음. (명시적으로 넣어 주어야 함)
<my-component
v-for="item in items" // 이 item 변수를 컴포넌트 영역..에 자동으로 주입하지는 않고
v-bind:item="item" // 이렇게 prop을 이용해서 명시적으로 넣어 주어야 해요.
v-bind:index="index"
:key="item.id" // 이것도 꼭 챙겨주세요 (v2.2~)
></my-component>
이벤트 핸들링
v-on
DOM 이벤트를 구독
methods
option 에 listener를 정의해서 쓴다.- 인라인도 가능하다
- 이벤트를 인자로 받을 땐
$event
예약어를 사용한다.
- 이벤트를 인자로 받을 땐
modifier
v-on:click.stop
v-on:submit.prevent
v-on:click.capture
v-on:click.self
- evt.target이 listener가 걸린 엘리먼트와 동일할 때v-on:click.once
- v2.1.4+v-on:scroll.passive
- v2.3+
chaning 가능, 순서 주의.
v-on:click.self.prevent
v-on:click.prevent.self
(v-on:click.prevent
랑 같은것 아닌가)
keyCode & alias
v-on:keyup.13
@keyup.enter
(부록)
MVC, MVP, MVVM…
구글링하다 ‘이런 패턴은 왜 쓰나’에 대한 한 문장 정리 맘에 들어서.
화면에 보여주는 로직과 실제 데이터 처리 로직을 분리