Nuxt.js 시작하려는데 React와 다르게 *.vue 파일에 HTML, CSS, JS 한꺼번에 정의하고 사소한 문법이 달라서 그 기반이 되는 Vue.js 부터 천천히 살펴보자 - 참고 문서
템플릿 구문
데이터 바인딩의 기본은 Mustache 구문 이다 : {{ JS 단일 표현식 }}
하지만 React JSX 와 달리 HTML 속성에서는 사용하지 못한다. 대신 v-bind 디렉티브를 이용하면 된다.
<a href="{{ link }}">링크</a> // (x)
<a v-bind:href="link">링크</a> // (o)
<a v-bind:href=[link]>링크</a> // (o) 2.6.0+ 추가
// 혹은 겹따옴포 안에 홑따옴표로 정의
<a v-bind:href="'https://www.naver.com'">네이버</a> // (o)
<script>
// Vue instance
data: {
link: "https://www.naver.com"
}
</script>
또한 하위 컴포넌트props 전달시 falsy 한 boolean 값을 전달하면 해당 element에 포함되지 않는다.
DOM selector
const data = {
message: 'hello world'
}
cont app = new Vue({
el: 'selector',
data: data // 여기에 변수 선언
})
// 참고로 속성 변경을 막고자 한다면
Object.freeze(data);
디렉티브
v-* 접두사가 있는 특수 속성
(사용방법) v-*:동적_전달인자 = "바인딩되는 객체_JS_단일표현식"
동적 전달인자 (콜론 뒤에 오는 속성 이름)는 v-*:href 처럼 직접 지정할 수도 있고 v-*:[변수] 처럼 Vue instance 속성으로 정의된 변수를 사용할 수 있다. 이때 없거나 null 인 경우 해당 코드는 작동하지 않는다. 이 외에는 string 변환이 예상되고 작동한다. 그리고 바인딩되는 객체 또한 v-for을 제외하고 단일 JS 표현식을 정의할 수 있다. 예시로, " 'text-' + variable " 처럼 겹따옴표 내부에 text와 변수를 혼용할 수 있다.
시작에 앞서 만약 디렉티브를 사용하는 tag 내부에 여러 엘리먼트 블럭을 렌더링한다면 <template> tag를 사용해야한다.
1. v-bind : 하위 컴포넌트에 전달될 porps 혹은 및 내부 content로 출력될 데이터 제어
<tag v-bind:prop1 ="message" />
// 자주 사용되는 v-bind 약어 제공됨.
<tag :prop1 ="message" />
Vvue instance를 이용해 동적으로 제어 가능하다.
또한 나중에 분리된 하위 component에 prop 전달 시 사용되겠다.
<div id="app-7">
<ol>
<todo-item
v-for="item in groceryList"
v-bind:todo="item" // -> todo-item 컴포넌트에 props 전달을 위해
v-bind:key="item.id"
></todo-item>
</ol>
</div>
<script>
Vue.component('todo-item', {
props: ['todo'],
template: '<li>{{ todo.text }}</li>'
})
var app7 = new Vue({
el: '#app-7',
data: {
groceryList: [
{ id: 0, text: 'Vegetables' },
{ id: 1, text: 'Cheese' },
{ id: 2, text: 'Whatever else humans are supposed to eat' }
]
}
})
</script>
2. v-if : 조건문 제어
조건문 제어가 간편하고만 JSX 내부에서 3항 연산자 안써도 되고
<tag v-if="booleanValue" > seen-toggle 데이터 </tag>
<tag v-else-if="booleanValue"> 조건에 따라 </tag> // -> 2.1.0+
<tag v-else> 다 falsy 하면 </tag>
이때, 처음부터 렌더링하지 않으면 내부 데이터가 새로이 리셋되지 않을 수 있다. (만약 toggle 했을 때 이전 입력값이 여전히 남아있을 수 있음) 이를 원치 않는다면 key 속성을 추가해준다.
<template v-if="loginType === 'username'">
<label>사용자 이름</label>
<input placeholder="사용자 이름을 입력하세요" key="username-input">
</template>
<template v-else>
<label>이메일</label>
<input placeholder="이메일 주소를 입력하세요" key="email-input">
</template>
참고로 v-show 디렉티브도 조건부 렌더링 방법인데 이는 DOM에 남아있으며 display CSS 속성을 변경할 뿐이다.
3. v-for
2.2.0 이상부터는 v-for 사용 시 key 가 필수적이다.
3-1. v-for 배열
<li v-for="(item, idx) in items"> {{ item.message }} </li>
<!-- of 문법도 가능
<li v-for="(item, idx) of items"> {{ item.message }} </li>
-->
<script>
var example1 = new Vue({
el: '#example-1',
data: {
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
</script>
배열 변경 감지
Vue가 감시 가능한 배열 변이 메소드는 push, pop, shift, splice, sort, reverse 등 이며 배열에 직접 할당함으로써 바꿀 수 있다. 만약 인덱스로 배열에 있는 항목 변경한다면 감지하지 못한다. 대신 Vue.set 함수 사용을 권장한다.
var vm = new Vue({
data: {
items: ['a', 'b', 'c']
}
})
vm.items[1] = 'newValue' // (x)
Vue.set(vm.items, 1, 'newValue') // o)
3-2. v-for 객체
<ul id="v-for-object" class="demo">
<li v-for="(value, key, index) in object">
{{ value }}
</li>
</ul>
<!--객체에서 index는 일관적이지 않아서.. 그닥 비추 -->
<script>
new Vue({
el: '#v-for-object',
data: {
object: {
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
}
}
})
</script>
객체 변경 감지
속성 값 변경은 감지하지만, 속성 추가 및 삭제를 감지하지 못한다. 이또한 Vue.set 함수 혹은 Object.assign() 으로 새로이 할당할 것을 권장한다.
var vm = new Vue({
data: {
userProfile: {
name: 'Anika'
}
}
})
Vue.set(vm.userProfile, 'newKey', 'newValue');
vm.userProfile = Object.assign({}, vm.userProfile, {
'newKey' : 'newValue'
})
3-3. v-for Ragne
숫자 반복도 가능하다.
<div>
<span v-for="n in 10">{{ n }} </span>
</div>
4. v-on 이벤트리스너 연결
<button v-on:"clickHandler"> click </button>
// 자주 사용되는 v-on 약어 제공됨.
<tag @click ="clickHandler" />
// 혹은 함수 인자 전달도 가능함.
// DOM event 객체는 $event 로 전달한다.
<button v-on:"clickHandler(전달인자들, $event)"> click </button>
<script>
...,
method: {
clickHandler: function(전달인자들,event){
//이게 연결됨!
}
}
</script>
대박, 데이터 뿐만 아니라 함수도 동적 할당 됨. 아래 예시처럼 기존 함수를 버튼을 누를 때마다 click이 출력되는 함수로 바꿈.
그럼 여기서 궁금한게, Vue instance 에서는 data, methods 등으로 분리되지만 instance 의 속성으로 바로 접근 가능한데 그럼 전체 data 속성은 어떻게 들어가는거지 싶어서 보니
Observer 라는 객체로 정의되고 instace._data (혹은 instance.$data) 속성으로 존재함.
이벤트 수식어
이벤트 핸들러 내부에 event.preventDefault()와 같은 자주 사용하는 메소드를 접미사로 제공한다. 접미사는 여러개를 사용하며 순서가 반영된다. (이벤트 관련 메소드를 잘 안써봐서 캡처링, 버블링 때 유용할 듯)
<!-- 클릭 이벤트 전파가 중단됩니다 -->
<a v-on:click.stop="doThis"></a>
<!-- 제출 이벤트가 페이지를 다시 로드 하지 않습니다 -->
<form v-on:submit.prevent="onSubmit"></form>
키보드 이벤트 수식어
이거 대박이다. ✍ v-on:keyup.enter 처럼 특징 키코드 없이 사용할 수 있다. 개꿀
혹여 별칭 key가 아니라 특수한 키코드라면 전역 객체로 지정할 수 있다.
// `v-on:keyup.f1`을 사용할 수 있습니다.
Vue.config.keyCodes.f1 = 112
조합키 누름 여부도 확인할 수 있음 : .exact
5. v-model 입력 상태를 양방향으로 바인딩
대박 이건 React에서 불편했던 input tag value, onChange , state 정의해야하는 번거로움을 줄여주겠다!
그런데 이상한 주의사항이있다.
- 한국어는 한글자씩 느리게 업데이트됨. - input 이벤트 대신 사용
- HTML에서 정의한
value,checked, selected 속성을 무시한다. - Vue 인스턴스 내부 데이터 옵션에 값을 부여하자. - text, textarea 태그 - value 속성과 input 이벤트를 사용
- 체크 박스, 라디오 버튼들은 checked 속성과 change 이벤트 사용
- select 태그는 value 속성과 change를 이벤트로 사용
뭐지.. 왜 이렇게 예와사항이 많아? 다 된밥에 재 뿌린 거 같은 이 기분은, Vue 가 너무 좋다고 생각했다가 기분이 잡...
사용자 정의 입력 컴포넌트 - 한글을 위해서 시도해볼만함.
<input
v-bind:value="something"
v-on:input="something = $event.target.value">
// 혹은
<custom-input
:value="something"
@input="value => { something = value }">
</custom-input>
@input 이벤트를 좀더 공부할 필요가 있어보임 (event.target.value 를 바로 받는 듯 하네)
<input type="checkbox" />
- 배열로 다루기
필수로 value 속성을 선언하고 v-model 에 바인딩된 변수 타입이 배열이면 해당 값들이 요소로 들어간다. (혹은 삭제)
여러 checkbox 타입의 input 태그가 같은 v-model을 바인딩하고 있으면 배열에 추가됨 (혹은 삭제) - 일반적으로
value를 지정하지 않고 v-model에 배열을 바인딩하면 null 이 들어간다.
value를 지정하지 않고 v-model에 빈문자열을 바인딩하면 boolean 값을 가진다.
(만약 여러개가 문자열로 초기화된 같은 v-model을 공유하고 있다면 함께 controll 됨)
<input type="radio" />
하위 option 들끼리 같은 v-model 을 공유하기. 이때, 각 value 부여하기
<select />
select tag에서 v-model을 바인딩하고 하위 option 심플하게 정의 (value 옵션을 넣지않으면 textContent가 value 됨)
이때, 초기값에 어떤 옵션에도 없으면 iOS에서는 변경 이벤트가 발생하지 않아 빈 옵션을 설정함.
<select v-model="selected">
<option disabled value="">Please select one</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<script>
// Vue instance data 옵션에
selected: ''
</script>
select tag에 multiple 속성 넣고 [shift] 눌러서 다중 선택하면 data 옵션에서 배열로 초기화하지 않아도 여러개가 들어감.
v-for 로 동적 옵션 렌더링 필수 숙지!
<option v-for="option in options" v-bind:value="option.value">
{{ option.text }}
</option>
<script>
new Vue({
el: '...',
data: {
selected: 'A',
options: [
{ text: 'One', value: 'A' },
{ text: 'Two', value: 'B' },
{ text: 'Three', value: 'C' }
]
}
})
</script>
v-model 수식어
- .trim 좌우 띄어쓰기 삭제
- .number 숫자로 형변환
- .lazy change 이벤트 이후에 동기화
무슨말?
추가 내용은 읽다만 곳부터 - 여기
컴포넌트 등록
주의사항이 있다.
- template : 항상 문자열 템플릿을 사용하는 것이 좋다.
- data
- 생성된 컴포넌트가 같은 data context를 갖지 않도록 새로운 객체를 리턴할 수 있도록 반드시 함수여야한다.
- React의 state 같은 개념인듯. - props
- 부모로부터 물려받으며 데이터와 마찬가지로 템플릿 내부에서 사용가능.
- 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
}
}
}
})
1. 전역 등록
<script>
// 전역으로 등록
Vue.component( tag-이름, {
template: '<div>{{ myMessage }}</div>', // camelCase로 사용함
props: [ myMessage, ... ]
} )
// 루트 인스턴스 생성
new Vue({
el: '#example'
})
</script>
<!-- 최종 렌더된 DOM -->
<!-- 부모 컴포넌트로부터 propr으로 데이터 물려받음 (kebab-case 로 정의됨) -->
<div id="example" my-message="사용자 정의 컴포넌트 입니다!">
<div>사용자 정의 컴포넌트 입니다!</div>
</div>
2. local 등록
<script>
// local 등록
var Child = {
template: '<div>사용자 정의 컴포넌트 입니다!</div>'
}
// 루트 인스턴스 생성
new Vue({
// ...
components: {
'my-component': Child
}
})
</script>
<!-- 최종 렌더된 DOM -->
<div id="example">
<div>사용자 정의 컴포넌트 입니다!</div>
</div>
3. 동적 컴포넌트
뭔가 routing 과 비슷한데...
<script>
var vm = new Vue({
el: '#example',
data: {
currentView: 'home'
},
components: {
home: { /* ... */ },
posts: { /* ... */ },
archive: { /* ... */ }
}
})
</script>
<component v-bind:is="currentView">
<!-- vm.currentView가 변경되면 컴포넌트가 변경됩니다! -->
</component>
특정 엘리먼트 사용자 지정 컴포넌트 사용 불가
<ul>,<ol>,<table>과<select> 은 하위 엘리먼트가 제한되어있다. is 와 같은 특수 속성으로 사용자 지정 컴포넌트를 연결.
<!-- 잘못됨
<table>
<my-row>...</my-row>
</table>
-->
<!-- 올바른 예시 -->
<table>
<tr is="my-row"></tr>
</table>
부모-자식 관계
React와 비슷하지만 Lifting state 개념을 Emit Events 라고 일컫는다.
(참고로 자식 컴포넌트 참조시 ref 에 등록하고 parent.$refs.xx 로 접근 가능, React Hook 같다.)
숫자 props 전달
만약 일반적인 prop으로 전달하면 문자열로 인식된다.
<comp v-bind:some-prop="1"></comp>
Routing
왜 간단한거지..
참고
Vue instance
위 개념들을 훑어보면서 자연스럽게 Vue instance 를 사용하였다. 하지만 좀 더 구체적으로 살펴보자.
const vm = new Vue({
/*
※ 주의사항 ※
this context 는 Vue instance 이다.
허나, 함수 선언 시 화살표 함수를 사용하면 this 를 갖지 않기 때문에 사용을 지양한다.
*/
el: 'selector',
data: data, // data 밑에 정의된 속성들은 반응형으로 변경 시 re-rendering된다.
methods: {
fn1
},
/* Lifecycle HOOK */
created: fn() {}, // 인스턴스 생성된 후 호출됨
mounted: fn() {}, // first render
updated: fn() {}, // re-render
destroyed: fn() {} // DOM에서 지워짐
})
Vue Lifecyle Diagram
React와 같은 듯 다르다. 특히 Vue instance가 create 될 때의 시점이 존재한다.
computed and watch option
computed 내부 속성에 복잡한 로직 함수를 정의할 수 있고 이는 기존 data 사용 시 자동으로 의존 관계가 형성되어 변경사항이 반영된다. 물론 method 속성에 정의해서 함수( ) 로 실행시켜 사용할 수 있다. 그럼 둘의 차이점은 무엇일까? 전자는 연결된 데이터가 변경될 때만 계산되어 캐싱 (저장) 된다는 것이고 바뀌지 않으면 기존 값을 유지한다. 즉 계산 시간을 절약할 수 있다. 후자는 렌더링 할 때마다 매번 계산된다. 예시로 Date.now( ) 를 return 하면 computed 속성의 경우 절대로 업데이트되지 않는다. 둘의 차이점을 이해하고 적절히 사용하면될 듯.
// 방법 1 computed 사용
<p>뒤집힌 메시지: "{{ reversedMessage }}"</p>
<script>
// Vue instance 선언시
computed: {
// 계산된 getter
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
</script>
// 방법 2 method 사용
<p>뒤집힌 메시지: "{{ reversedMessage() }}"</p>
<script>
// Vue instance 선언시
methods: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
</script>
watch 속성 (명령형 프로그래밍)은 특정 data 의 변화를 감지하는데 변경해야할 다른 데이터가 있다면 자동으로 해주는 computed 속성 (선언형 프로그래밍)가 더 나은 듯. 만약 특정 행동(예를 들어 비동기 연산)을 취해야한다면 watch가 낫고! 직접 경험해봐야 할 듯. 아, 참고로 input 값 data에 반영은 v-model 디렉티브 사용하면 됨.
꽤 괜찮은 예시 - watch 속성을 이용해 입력 전, 중, 후 에 따라 메세지를 상이하게 출력
Binding Class and Style
일반적으로 v-bind 를 사용해 class, style이 처리된다. 하지만 값으로 입력되는 최종 문자열 연산에 대한 오류를 줄이기 위해 향상된 기능을 제공한다.
대박 isActive 값에 따라 active 클래스 존재 여부를 토글할 수 있다. modal 제어에 편하겠다잉. 그리고 일반 class 속성과 공존할 수 있다. 토글 여부 필요하지 않으면 그냥 class 로 바로 정의하면 되겠다.
<div
class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }"
></div>
<!--
// 2번째 방법 객체 반환
<div v-bind:class="classObject"></div>
// 3번째 방법 배열 전달
<div v-bind:class="[activeClass, errorClass]"></div>
-->
<script>
data: {
isActive: true,
hasError: false,
// 2번째 방법
classObject: {
active: true,
'text-danger': false
},
// 3번째 방법
activeClass: 'active',
errorClass: 'text-danger'
}
</script>
<!-- 최종 렌더링된 DOM 구조 -->
<div class="static active"></div>
그리고 사용자 정의 component 가 정의될 때, 사용될 때 모두 class 값을 부여할 수 있는데 덮어씌우지 않고 추가됨.
스타일 적용은 템플릿이 간단하도록 분리하는게 더 낫다는데.....
<div v-bind:style="styleObject"></div>
<script>
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
</scipt>
아니, CSS 로 분리하는 게 더 낫지 않나??
질문
1. is, for props 는 뭐지??
여기 에서 출발한 의문
is는 component를 연결하는 것 같지만, for 은 뭐지.... 무튼 예시 코드 매우 간단해서 좋다. remove도 간편하고
'개발 지식 A+ > FE' 카테고리의 다른 글
Web 3D rendering (0) | 2023.09.28 |
---|---|
Nuxt.js 시작하기 (0) | 2021.02.21 |
Redux (0) | 2021.01.26 |
Hooks 공식문서로 이해하기 (0) | 2021.01.26 |
this 키워드 (0) | 2021.01.26 |