Vue with Typescript
Vue는 클래스라기보단 Object이다.
Vue.js 구조 이해
new Vue()
로 생성되는 인스턴스는 템플릿 영역의 가상DOM과 동적으로 바인딩되는 것이라고 개념을 잡으면 이해하기 쉽다.
- 인스턴스 안에 데이터가 존재하고 데이터가 가상 DOM과 바인되는 순간 의존성이 생성된다고 보면 된다.
- mounted 된 후에 데이터가 변경되면 의존성이 생성된 DOM을 위주로 re-render 및 patch가 진행된다.
vue 인스턴스 라이프 사이클 훅
Vue 세계에서 “컨트롤러”의 컨셉이 어디에 있는지 궁금할 수 있습니다. 답은 컨트롤러가 없습니다. 컴포넌트의 사용자 지정 로직은 이러한 라이프사이클 훅으로 분할됩니다.
- HTML의 경우 render function으로 생성된다.
- 내부적으로 Vue는 템플릿을 가상 DOM 렌더링 함수로 컴파일 합니다.
- 가상 DOM 개념에 익숙하고 JavaScript의 기본 기능을 선호하는 경우 템플릿 대신 렌더링 함수를 직접 작성할 수 있으며 선택사항으로 JSX를 지원합니다.
- 템플릿이 존재하면 템플릿을 render 대상으로 잡고 템플릿이 없으면 outer HTML을 render 대상으로 잡는다.
- 가상DOM의 경우 vue 인스턴스안에 존재한다. mounted의 경우 가상DOM이 실제DOM로 구현될 때를 의미한다.
vm.$el
- 옵션으로 들어간 data, methods는 vue 인스턴스로 접근이 가능하다.
vm.데이터', 'vm.메소드
Vue.js 컴포넌트 이해
Vue 컴포넌트는 Vue 인스턴스이기도 합니다. 그러므로 모든 옵션 객체를 사용할 수 있습니다. (루트에만 사용하는 옵션은 제외) 그리고 같은 라이프사이클 훅을 사용할 수 있습니다.
- 컴포넌트의 경우 data는 함수형이어야 하는 조건이 다르다.
Props 는 외부 환경이 데이터를 컴포넌트로 전달하도록 허용합니다.
이벤트를 통해 컴포넌트가 외부 환경에서 사이드이펙트를 발생할 수 있도록 합니다.
슬롯 을 사용하면 외부 환경에서 추가 컨텐츠가 포함 된 컴포넌트를 작성할 수 있습니다
data 속성
data에 있는 속성들은 인스턴스가 생성될 때 존재한 것들만 반응형이라는 것입니다.
vue 인스턴스
Vue 인스턴스는 데이터 속성 이외에도 유용한 인스턴스 속성 및 메소드를 제공합니다. 다른 사용자 정의 속성과 구분하기 위해 $ 접두어를 붙였습니다.
directive
디렉티브는 v- 접두사가 있는 특수 속성입니다. 디렉티브 속성 값은 단일 JavaScript 표현식 이 됩니다. (나중에 설명할 v-for는 예외입니다.)
- 자바스크립트 표현식으로 변환된다는 것이 중요.
- : 은 전달인자로 directive에게 대상이 되는 속성을 알려준다.
- 수식어는 점으로 표시되는 특수 접미사로, 디렉티브를 특별한 방법으로 바인딩 해야 함을 나타냅니다. 예를 들어, .prevent 수식어는 트리거된 이벤트에서 event.preventDefault()를 호출하도록 v-on 디렉티브에게 알려줍니다.
<form v-on:submit.prevent="onSubmit"> ... </form>
directive 약어
<!-- 전체 문법 -->
<a v-bind:href="url"> ... </a>
<!-- 약어 -->
<a :href="url"> ... </a>
<!-- shorthand with dynamic argument (2.6.0+) -->
<a :[key]="url"> ... </a>
<!-- 전체 문법 -->
<a v-on:click="doSomething"> ... </a>
<!-- 약어 -->
<a @click="doSomething"> ... </a>
<!-- shorthand with dynamic argument (2.6.0+) -->
<a @[event]="doSomething"> ... </a>
메소드 & computed & watched
이 예제에서는 computed 속성인 reversedMessage를 선언했습니다. 우리가 작성한 함수는 vm.reversedMessage속성에 대한 getter 함수로 사용됩니다.
메소드와 computed의 차이는 캐싱여부이다. computed의 경우 의존된 데이터가 변경이 일어날 경우 함수를 호출하고 그렇지 않으면 캐쉬된 데이터를 반환한다.
console.log(vm.reversedMessage) // => '요세하녕안'
vm.message = 'Goodbye'
console.log(vm.reversedMessage) // => 'eybdooG'
computed 선언형 프로그래밍, watched는 명령형 프로그래밍 방식의 차이점을 알고 사용하길 바람.
v-for
Vue는 감시중인 배열의 변이 메소드를 래핑하여 뷰 갱신을 트리거합니다. 래핑된 메소드는 다음과 같습니다.
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
콘솔을 열고 이전 예제의 items 배열로 변이 메소드를 호출하여 재생할 수 있습니다.예: example1.items.push({ message: 'Baz' })
v-on
왜 HTML로 된 리스너를 사용합니까
이 모든 이벤트 청취 접근 방법이 관심사의 분리(“separation of concerns”)에 대한 오래된 규칙을 어긴다고 생각할 수 있습니다. 모든 뷰 핸들러 함수와 표현식은 현재 뷰 처리 하는 ViewModel에 엄격히 바인딩 되기 때문에 유지보수가 어렵지 않습니다. 실제로 v-on을 사용하면 몇가지 이점이 있습니다.
HTML 템플릿을 간단히 하여 JavaScript 코드 내에서 핸들러 함수 구현을 찾는 것이 더 쉽습니다.
JavaScript에서 이벤트 리스너를 수동으로 연결할 필요가 없으므로 ViewModel 코드는 순수 로직과 DOM이 필요하지 않습니다. 이렇게 하면 테스트가 쉬워집니다.
ViewModel이 파기되면 모든 이벤트 리스너가 자동으로 제거 됩니다. 이벤트 제거에 대한 걱정이 필요 없어집니다.
v-model 작동방식
v-model은 내부적으로 서로 다른 속성을 사용하고 서로 다른 입력 요소에 대해 서로 다른 이벤트를 전송합니다 :
- text 와 textarea 태그는 value속성과 input이벤트를 사용합니다.
- 체크박스들과 라디오버튼들은 checked 속성과 change 이벤트를 사용합니다.
- Select 태그는 value를 prop으로, change를 이벤트로 사용합니다.
반응형
이미 생성된 vue instance에 새로운 속성을 추가하는 것은 반응형을 일으키지 않는다.
다음 전역 함수를 통해서 설정이 가능하다.Vue.set(object, propertyName, value
vm.$set(vm.userProfile, 'age', 27)
컴포넌트 통신
HTML 속성은 대소 문자를 구분하지 않으므로 문자열이 아닌 템플릿을 사용할 때 camelCased prop 이름에 해당하는 kebab-case(하이픈 구분)를 사용해야 합니다.
Vue.component('child', {
// JavaScript는 camelCase
props: ['myMessage'],
template: '<span>{{ myMessage }}</span>'
})
<!-- HTML는 kebab-case -->
<child my-message="안녕하세요!"></child>
props 유효성 검증
Vue.component('example', {
props: {
// 기본 타입 확인 (`null` 은 어떤 타입이든 가능하다는 뜻입니다)
propA: Number,
// 여러개의 가능한 타입
propB: [String, Number],
// 문자열이며 꼭 필요합니다
propC: {
type: String,
required: true
},
// 숫자이며 기본 값을 가집니다
propD: {
type: Number,
default: 100
},
// 객체/배열의 기본값은 팩토리 함수에서 반환 되어야 합니다.
propE: {
type: Object,
default: function () {
return { message: 'hello' }
}
},
// 사용자 정의 유효성 검사 가능
propF: {
validator: function (value) {
return value > 10
}
}
}
})
양방향 .sync
<comp :foo.sync="bar"></comp>
<comp :foo="bar" @update:foo="val => bar = val"></comp>
this.$emit('update:foo', newValue)
v-model 구조
<input v-model="something">
<!-- 데이터를 바인딩하고 이벤트를 걸어 v-model 구조 -->
<input
v-bind:value="something"
v-on:input="something = $event.target.value">
사용자 정의 컴포넌트 v-model
- v-model을 사용하는 컴포넌트의 경우 다음을 충족해야한다.
- value prop를 가집니다.
- 새로운 값으로 input 이벤트를 내보냅니다.
<custom-input
:value="something"
@input="value => { something = value }">
</custom-input>
model 옵션을 통해서 v-model에 변수명을 변경할 수 있다.
Vue.component('my-checkbox', {
model: {
prop: 'checked', //props는 checked
event: 'change' // event는 change로 변경됨.
},
props: {
// 다른 목적을 위해 `value` prop를 사용할 수 있습니다.
checked: Boolean,
value: String
},
// ...
})
<my-checkbox
:checked="foo"
@change="val => { foo = val }"
value="some value">
</my-checkbox>
비 부모-자식간 통신
때로는 두 컴포넌트가 서로 통신 할 필요가 있지만 서로 부모/자식이 아닐 수도 있습니다. 간단한 시나리오에서는 비어있는 Vue 인스턴스를 중앙 이벤트 버스로 사용할 수 있습니다.
var bus = new Vue()
// 컴포넌트 A의 메소드
bus.$emit('id-selected', 1)
// 컴포넌트 B의 created 훅
bus.$on('id-selected', function (id) {
// ...
})
보다 복잡한 경우에는 전용 상태 관리 패턴을 고려해야합니다
범위 컴파일
<child-component>
{{ message }}
</child-component>
상위 템플릿의 모든 내용은 상위 범위로 컴파일됩니다. 하위 템플릿의 모든 내용은 하위 범위에서 컴파일됩니다.
자식 컴포넌트
자식 컴포넌트 참조
props나 이벤트가 있었음에도 불구하고 때때로 JavaScript로 하위 컴포넌트에 직접 액세스 해야 할 수도 있습니다. 이를 위해 ref 를 이용하여 참조 컴포넌트 ID를 자식 컴포넌트에 할당해야 합니다. 예:
<div id="parent">
<user-profile ref="profile"></user-profile>
</div>
var parent = new Vue({ el: '#parent' })
// 자식 컴포넌트 인스턴스에 접근합니다.
var child = parent.$refs.profile
ref가 v-for와 함께 사용될 때, 얻을 수 있는 ref는 데이터 소스를 미러링하는 자식 컴포넌트를 포함하는 배열이 될 것입니다.
$refs
는 컴포넌트가 렌더링 된 후에만 채워지며 반응적이지 않습니다. 그것은 직접 자식 조작을 위한 escape 해치를 의미합니다 - 템플릿이나 계산 된 속성에서 $refs
를 사용하지 말아야합니다.
싱글 페이지 vue
vue-loader를 통해서 싱글 페이지 vue를 구성할 수 있다.
vue cli
CLI는 여러분을 위해 대부분의 구성을 관리하지만, 자체 구성 옵션을 통한 세밀한 사용자 정의 구성도 허용합니다.
빌드 설정을 처음부터 직접 구성하길 원한다면, Webpack과 vue-loader을 수동으로 구성해야 합니다.
Webpack에 대한 더 자세한 내용은 Webpack 공식 문서와 Webpack Academy를 확인하세요.
라우터
페이지간의 이동을 라우터라고 한다. 뷰 컴포넌트를 사용한 기본적인 vue 라우팅은 다음과 같다.
const NotFound = { template: '<p>Page not found</p>' }
const Home = { template: '<p>home page</p>' }
const About = { template: '<p>about page</p>' }
const routes = {
'/': Home,
'/about': About
}
new Vue({
el: '#app',
data: {
currentRoute: window.location.pathname
},
computed: {
ViewComponent () {
return routes[this.currentRoute] || NotFound
}
},
render (h) { return h(this.ViewComponent) }
})
vue-router
vue에 라우터 기능을 제공한다.
상태 관리
공통 변수를 data로 관리하여 상태를 관리할 수 있다. 하위 인스턴스에서는 this.$root.$data
를 통해서 데이터에 접근이 가능하다. 이런식으로 상태를 관리하면 아비규환이 될 것이다.
const sourceOfTruth = {}
const vmA = new Vue({
data: sourceOfTruth
})
const vmB = new Vue({
data: sourceOfTruth
})
이를 보완한 것이 store 패턴이다.
스토어 패턴을 통해서 데이터 변경 로그를 남기도록 하는 것.
컴포넌트가 store에 속한 상태를 직접 변이 시킬 수 없지만 store에 조작을 수행하도록 알리는 이벤트를 보내야하는 컨벤션을 계속 개발할 때 결국 Flux 아키텍처에 다다르게 됩니다.
var store = {
debug: true,
state: {
message: 'Hello!'
},
setMessageAction (newValue) {
if (this.debug) console.log('setMessageAction triggered with', newValue)
this.state.message = newValue
},
clearMessageAction () {
if (this.debug) console.log('clearMessageAction triggered')
this.state.message = ''
}
}