List of 300 VueJS Interview Questions
- ๐ ๋ณธ ๋ฌธ์๋ sudheerj์ vuejs-interview-questions์ ๋ฒ์ญ๋ณธ์ ๋๋ค.
- โญ ์ด ํ๋ก์ ํธ๊ฐ ๋ง์์ ๋์ จ๋ค๋ฉด STAR๋ฅผ ๋๋ฌ์ฃผ์ธ์.
- โ๏ธ ํ ๋ฆฌํ์คํธ๋ ์ธ์ ๋ ํ์์ ๋๋ค.
-
Vue.js๋ ์ฌ์ฉ์ ์ธํฐํ์ด์ค๋ฅผ ๋ง๋ค๊ธฐ ์ํ ์ง๋ณด์ ์ธ ํ๋ ์์ํฌ์ ๋๋ค. ํต์ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋
๋ทฐ ๋ ์ด์ด
๋ง ์ด์ ์ ๋ง์ถ์ด, ๋ค๋ฅธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ๊ธฐ์กด ํ๋ก์ ํธ์์ ํตํฉ์ด ์ฝ์ต๋๋ค. -
์๋์ ํญ๋ชฉ๋ค์ VueJS์ ์ฃผ์ ํน์ง๋ค์ ๋๋ค.
- ๊ฐ์ DOM(Virtual DOM): VueJS์์๋ ReactJS, Ember ํ๋ ์์ํฌ์ ์ ์ฌํ๊ฒ ๊ฐ์ DOM์ ์ฌ์ฉํฉ๋๋ค. ๊ฐ์ DOM์ ์๋ณธ HTML DOM์ ํํํ๋ ๋ฉ๋ชจ๋ฆฌ ์์ ๊ฐ๋ฒผ์ด DOM ํธ๋ฆฌ๋ก, ์๋ณธ DOM์ ์ํฅ์ ๋ฏธ์น์ง ์๊ณ ์ ๋ฐ์ดํธ๋ฅผ ํ ์ ์์ต๋๋ค.
- ์ปดํฌ๋ํธ(Components): VueJS ์ดํ๋ฆฌ์ผ์ด์ ์์ ์ฌ์ฌ์ฉํ ์ ์๋ ์๋ฆฌ๋จผํธ๋ค์ ๋ง๋ค ์ ์์ต๋๋ค.
- ํ ํ๋ฆฟ(Templates): VueJS๋ Vue ์ธ์คํด์ค ๋ฐ์ดํฐ์ DOM์ ์ ๊ทผํ ์ ์๋ HTML ๊ธฐ๋ฐ์ ํ ํ๋ฆฟ์ ์ ๊ณตํฉ๋๋ค.
- ๋ผ์ฐํ (Routing): ํ์ด์ง์ ์ ํ์ vue-router๋ฅผ ์ด์ฉํฉ๋๋ค.
- ์ ์ฉ๋(Light weight): VueJS๋ ๋ค๋ฅธ ํ๋ ์์ํฌ์ ๋น๊ตํด ์ ์ฉ๋์ ๋๋ค.
-
๋ผ์ดํ์ฌ์ดํด ํ (Lifecycle hook)์ ์ฌ์ฉ์ค์ธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ด๋ค ์์๋ก ๋์ํ๋์ง๋ฅผ ์๋ ค์ฃผ๋ ์ญํ ์ ํฉ๋๋ค. ํ ์ ์ด์ฉํด ์ปดํฌ๋ํธ๊ฐ ์ธ์ ์์ฑ๋๊ณ , ์ธ์ DOM์ ์ถ๊ฐ๋๋ฉฐ, ์ธ์ ์ ๋ฐ์ดํธ๋๊ณ ์ธ์ ์ฌ๋ผ์ง๋์ง ์ ์ ์์ต๋๋ค. ์๋์ ๋ค์ด์ด๊ทธ๋จ์ ํตํด VueJS์ ์ ๋ฐ์ ์ธ ๋ผ์ดํ์ฌ์ดํด์ ํ์ธํ ์ ์์ต๋๋ค.
-
Creation(์ด๊ธฐํ): Create ํ ์ ์ปดํฌ๋ํธ๊ฐ DOM์ ์ถ๊ฐ๋๊ธฐ ์ ์ ์คํ๋๋ ๋จ๊ณ์ ๋๋ค. ํด๋ผ์ด์ธํธ์ ์๋ฒ๊ฐ ๋ ๋๋ง ๋จ๊ณ ์ ์ ์ปดํฌ๋ํธ์ ์ค์ ํด์ผ ํ ๊ฒ๋ค์ด ์์ ๋ ์ฌ์ฉํ๋ ๋จ๊ณ์ ๋๋ค. ๋ค๋ฅธ ํ ๊ณผ๋ ๋ค๋ฅด๊ฒ, Create ํ ์ ์๋ฒ ์ฌ์ด๋ ๋ ๋๋ง์์๋ ์ง์๋๋ ํ ์ ๋๋ค.
- beforeCreate:
beforeCreate
ํ ์ ์ปดํฌ๋ํธ ์ด๊ธฐํ ๋จ๊ณ ์ค ๊ฐ์ฅ ์ฒ์์ผ๋ก ์คํ๋ฉ๋๋ค. ์ด ํ ์์๋ ์ปดํฌ๋ํธ์ data๋ฅผ ๊ด์ฐฐํ๊ณ , ์ด๋ฒคํธ๋ฅผ ์ด๊ธฐํํฉ๋๋ค. ์ด ๋จ๊ณ์์ data๋ ์์ง๊น์ง ๋ฐ์์ ์ด์ง ์์ผ๋ฉฐ, ์ปดํฌ๋ํธ์ ๋ผ์ดํ์ฌ์ดํด์์ ๋ฐ์ํ๋ ์ด๋ฒคํธ ์ญ์ ์ค์ ๋์ง ์์ ์ํ์ ๋๋ค.
new Vue({ data: { count: 10 }, beforeCreate: function () { console.log('Nothing gets called at this moment') // `this` points to the view model instance console.log('count is ' + this.count); } }) // count is undefined
- created:
created
ํ ์ Vue ์ธ์คํด์ค๊ฐ ์ด๋ฒคํธ๋ฅผ ์ค์ ํ๊ณ data๋ฅผ ๊ด์ฐฐํ ๋ ๋ฐ์ํฉ๋๋ค. ์ด ๋จ๊ณ์์ ํ ํ๋ฆฟ์ ์์ง ๋ง์ดํธ๋๊ฑฐ๋ ๋ ๋๋ง๋์ง ์์์ง๋ง, ์ด๋ฒคํธ๋ค์ด ํ์ฑํ๋๋ฉฐ data์ ๋ฐ์์ ์ผ๋ก ์ ๊ทผํ๋ ๊ฒ์ด ๊ฐ๋ฅํฉ๋๋ค.
new Vue({ data: { count: 10 }, created: function () { // `this` points to the view model instance console.log('count is: ' + this.count) } }) // count is: 10
์ฃผ์: Create ํ ์์๋ DOM์ ์ง์ ์ ๊ทผํ๊ฑฐ๋ ๋ง์ดํธํ ์๋ฆฌ๋จผํธ(
this.$el
)์ ์ง์ ์ ๊ทผํ ์ ์๋ค๋ ์ ์ ๊ธฐ์ตํ์ธ์. - beforeCreate:
-
Mounting(DOM ์ถ๊ฐ): Mount ํ ์ ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉ๋๋ ๋จ๊ณ๋ก, ์ปดํฌ๋ํธ๊ฐ ๋ ๋๋ง๋๊ธฐ ์ง์ ์ด๋ ์งํ์ ์ปดํฌ๋ํธ์ ์ ๊ทผํ ์ ์๋ ๋จ๊ณ์ ๋๋ค.
- beforeMount:
beforeMount
ํ ์ ์ปดํฌ๋ํธ๊ฐ DOM์ ์ถ๊ฐ๋๊ธฐ ์ง์ ์ ์คํ๋๋ ํ ์ ๋๋ค.
new Vue({ beforeMount: function () { // `this` points to the view model instance console.log(`this.$el is yet to be created`); } })
- mounted:
mounted
ํ ์ ๋ฐ์์ ์ธ data, ํ ํ๋ฆฟ, ๋ ๋๋ง๋ DOM(this.$el
) ๋ชจ๋์ ์ ๊ทผํ ์ ์์ด์ ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉ๋๋ ํ ์ ๋๋ค. ํํ ์ปดํฌ๋ํธ์์ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ์ธ๋ถ์์ ๊ฐ์ ธ์ค๋(fetch) ์ฉ๋๋ก ๋ง์ด ์ฌ์ฉ๋ฉ๋๋ค.
<div id="app"> <p>Iโm text inside the component.</p> </div> new Vue({ el: '#app', mounted: function() { console.log(this.$el.textContent); // I'm text inside the component. } })
- beforeMount:
-
Updating (์ฌ ๋ ๋๋ง): Update ํ ์ ์ปดํฌ๋ํธ ๋ด๋ถ์ ๋ฐ์์ ์ธ ์์ฑ์ด ๋ณํ๊ฑฐ๋, ๊ทธ ์ธ์ ๊ฒ๋ค์ด ์ฌ ๋ ๋๋ง์ ์ผ์ผํฌ ๋ ์คํ๋๋ ๋จ๊ณ์ ๋๋ค.
- beforeUpdate:
beforeUpdate
ํ ์ ์ปดํฌ๋ํธ์ data๊ฐ ๋ณ๊ฒฝ๋์ด ์ ๋ฐ์ดํธ ์ฌ์ดํด์ด ์์๋ ๋ ์คํ๋ฉ๋๋ค.
<div id="app"> <p>{{counter}}</p> </div> ...// rest of the code new Vue({ el: '#app', data() { return { counter: 0 } }, created: function() { setInterval(() => { this.counter++ }, 1000) }, beforeUpdate: function() { console.log(this.counter) // Logs the counter value every second, before the DOM updates. } })
- updated:
updated
ํ ์ ์ปดํฌ๋ํธ์ data๊ฐ ๋ณํ์ฌ ์ฌ ๋ ๋๋ง์ด ์ผ์ด๋ ํ์ ์คํ๋ฉ๋๋ค.
<div id="app"> <p ref="dom">{{counter}}</p> </div> ...// new Vue({ el: '#app', data() { return { counter: 0 } }, created: function() { setInterval(() => { this.counter++ }, 1000) }, updated: function() { console.log(+this.$refs['dom'].textContent === this.counter) // Logs true every second } })
- beforeUpdate:
-
Destruction(ํด์ฒด): Destruction ํ ์ ์ปดํฌ๋ํธ๋ฅผ ๋ ์ด์ ์ฌ์ฉํ์ง ์์ ๋ ์ฌ์ฉํ๋ ๋จ๊ณ์ ๋๋ค.
- beforeDestroy:
beforeDestroy
ํ ์ ์ปดํฌ๋ํธ๊ฐ ํด์ฒด๋๊ธฐ ์ง์ ์ ์คํ๋ฉ๋๋ค. ์ด ํ ์ ๋ฐ์์ ์ธ ์ด๋ฒคํธ๋ค์ด๋ data๋ค์ ํด์ฒดํ๋ ํ ์ผ๋ก ์ ํฉํฉ๋๋ค. ์ด ๋จ๊ณ์์ ์ปดํฌ๋ํธ๋ ์ฌ์ ํ ๋ฌธ์ ์์ด ์ ๋์ํฉ๋๋ค.
new Vue ({ data() { return { message: 'Welcome VueJS developers' } }, beforeDestroy: function() { this.message = null delete this.message } })
- destroyed:
destroyed
ํ ์ ์ปดํฌ๋ํธ๊ฐ ํด์ฒด๋๊ณ ๋ ์งํ์ ํธ์ถ๋ฉ๋๋ค. ๋ชจ๋ ์ง์์๋ค์ ๋ฐ์ธ๋ฉ์ด ํด์ ๋์์ผ๋ฉฐ, ์ด๋ฒคํธ ๋ฆฌ์ค๋๊ฐ ์ ๊ฑฐ๋ ์ํ์ ๋๋ค.
new Vue ({ destroyed: function() { console.log(this) // Nothing to show here } })
- beforeDestroy:
-
-
VueJS๋ ์กฐ๊ฑด์ ๋ฐ๋ผ ์๋ฆฌ๋จผํธ๋ฅผ ๋ณด์ฌ์ฃผ๊ฑฐ๋ ์จ๊ธธ ์ ์๋ ์ง์์๋ค์ ์ ๊ณตํฉ๋๋ค. ์ฌ์ฉํ ์ ์๋ ์ง์์๋ค์ v-if, v-else, v-else-if and v-show๊ฐ ์์ต๋๋ค.
1. v-if: v-if ์ง์์๋ ์กฐ๊ฑด์ ๋ฐ๋ผ DOM ์๋ฆฌ๋จผํธ๋ฅผ ์ถ๊ฐํ๊ฑฐ๋ ์ ๊ฑฐํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ์๋์ ๋ฒํผ์
isLoggedIn
์ ๊ฐ์ดfalse
๋ผ๋ฉด ๋ํ๋์ง ์์ต๋๋ค.<button v-if="isLoggedIn">Logout</button>
์๋ฆฌ๋จผํธ๋ค์
<template>
ํ๊ทธ๋ก ๊ฐ์ผ๋ค๋ฉด, ํ๋์ v-if๋ง์ผ๋ก ์ฌ๋ฌ ์๋ฆฌ๋จผํธ๋ค์ ์กฐ๊ฑด์ ์ค์ ํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์๋์ ๊ฐ์ดlabel
๊ณผbutton
ํ๊ทธ๋ฅผ ํ๋์ ์กฐ๊ฑด ์ง์์๋ก ์ ์ดํ ์ ์์ต๋๋ค.<template v-if="isLoggedIn"> <label> Logout </button> <button> Logout </button> </template>
2. v-else: ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์์
if
์ ์กฐ๊ฑด์ ๋ง์ง ์๋ ๊ฒฝ์ฐelse
๋ก ๋์ด๊ฐ๋ ๊ฒ์ฒ๋ผ, v-else ์ง์์๋ ์ธ์ ํ v-if ์ง์์ ๋๋ v-else-if ์ง์์๊ฐfalse
์ผ ๋๋ง ๊ทธ ๋ด์ฉ์ด ๋ํ๋ฉ๋๋ค. ์ด ์ง์์์๋ ์กฐ๊ฑด์ ์ง์ ํ ํ์๊ฐ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์๋์ ์์๋isLoggedIn
์ดfalse
์ผ ๋(์ฆ ๋ก๊ทธ์ธ ๋ ์ํ๊ฐ ์๋ ๋), v-else๋ฅผ ์ด์ฉํด ๋ก๊ทธ์ธ ๋ฒํผ์ ๋ณด์ฌ์ค๋๋ค.<button v-if="isLoggedIn"> Logout </button> <button v-else> Log In </button>
3. v-else-if: v-else-if ์ง์์๋ v-if ์ด์ธ์ ๋ค๋ฅธ ์กฐ๊ฑด์ ์ถ๊ฐ๋ก ํ์ธํด์ผ ํ ๋ ์ฌ์ฉํฉ๋๋ค. ์๋ฅผ ๋ค์ด, v-else-if ์ง์์๋ฅผ ์ด์ฉํด
ifLoginDisabled
์ ๊ฐ์ดtrue
์ผ ๋๋ ๋ก๊ทธ์ธ ๋ฒํผ ๋์ ํ ์คํธ๋ฅผ ๋ณด์ฌ์ค ์ ์์ต๋๋ค.<button v-if="isLoggedIn"> Logout </button> <label v-else-if="isLoginDisabled"> User login disabled </label> <button v-else> Log In </button>
4. v-show: v-show ์ง์์๋ v-if ์ง์์์ ๋น์ทํ ๊ธฐ๋ฅ์ ํ์ง๋ง, DOM์ ์๋ฆฌ๋จผํธ๊ฐ ์ถ๊ฐ๋ ์ํ์์ CSS์ display ๊ฐ์ ์ด์ฉํด ๋ณด์ฌ์ฃผ๊ณ ์จ๊น์ ๊ฒฐ์ ํฉ๋๋ค. v-show ์ง์์๋ ์กฐ๊ฑด๋ฌธ์ด ์์ฃผ ํ ๊ธ๋ ๋ ๊ถ์ฅ๋ฉ๋๋ค.
<span v-show="user.name">Welcome user,{{user.name}}</span>
-
v-show์ v-if์ ์ฃผ์ ์ฐจ์ด์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- v-if๋ ์กฐ๊ฑด์ด ์ผ์นํ๋ ์๋ฆฌ๋จผํธ๋ง DOM์ ๋ ๋๋งํ๋ ๋ฐ๋ฉด, v-show๋ ๋ชจ๋ ์๋ฆฌ๋จผํธ๋ฅผ DOM์ ๋ ๋๋งํ ํ CSS๋ฅผ ์ด์ฉํด ๋ด์ฉ์ ๋ณด์ฌ์ฃผ๊ฑฐ๋ ์จ๊น๋๋ค.
- v-if์ v-else-if์์๋ v-else๋ฅผ ์ฌ์ฉํ ์ ์์ง๋ง, v-show์์๋ ์ฌ์ฉํ ์ ์์ต๋๋ค.
- v-if๋ ํ ๊ธํ ๋ ๋์ ๋ ๋๋ง ๋น์ฉ์ด ๋ค์ง๋ง, v-show๋ ์ด๊ธฐ์ ๋ ๋๋ง ์์ ์์ ๋์ ๋น์ฉ์ด ๋ญ๋๋ค. ์ฆ, v-show๋ ์์๋ฅผ ์์ฃผ ์ผ๊ณ ๋๋ ๊ฒฝ์ฐ ์ฑ๋ฅ ์์ ์ด์ ์ด ์์ง๋ง, ์ด๊ธฐ ๋ ๋๋ง ์์ ์์๋ v-if๊ฐ ๋ ํจ์จ์ ์ ๋๋ค.
- v-if๋
<template>
ํ๊ทธ์์ ์ฌ์ฉํ ์ ์์ง๋ง v-show๋ ์ฌ์ฉํ ์ ์์ต๋๋ค.
-
v-for ์ง์์๋ ๋ฐฐ์ด์ด๋ ๊ฐ์ฒด๋ฅผ ์ํํ๋ฉด์ ๋ฐ๋ณต์ ์ธ ๋ ๋๋ง์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค.
- ๋ฐฐ์ด์ ๊ฒฝ์ฐ:
<ul id="list"> <li v-for="(item, index) in items"> {{ index }} - {{ item.message }} </li> </ul> var vm = new Vue({ el: '#list', data: { items: [ { message: 'John' }, { message: 'Locke' } ] } })
์๋ฐ์คํฌ๋ฆฝํธ ์ํ๋ฌธ๊ณผ ์ ์ฌํ๊ฒ,
in
์ธ์๋of
๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.- ๊ฐ์ฒด์ ๊ฒฝ์ฐ:
<div id="object"> <div v-for="(value, key, index) in user"> {{ index }}. {{ key }}: {{ value }} </div> </div> var vm = new Vue({ el: '#object', data: { user: { firstName: 'John', lastName: 'Locke', age: 30 } } })
-
๋ชจ๋ Vue ์ดํ๋ฆฌ์ผ์ด์ ์ Vue ํจ์๋ฅผ ์ด์ฉํด Vue ์ธ์คํด์ค๋ฅผ ์์ฑํ๋ฉด์ ๋์ํฉ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก
vm
(ViewModel์ ์ถ์ฝํ)์ด๋ผ๋ ๋ณ์๋ฅผ ์ด์ฉํด Vue ์ธ์คํด์ค๋ฅผ ์ฐธ์กฐํฉ๋๋ค. ์๋์ ๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก Vue ์ธ์คํด์ค๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.var vm = new Vue({ // options })
์์ ์ฝ๋์์ ๋ณผ ์ ์๋ฏ, ์ต์ ์ ์ค์ ํ๊ธฐ ์ํ ๊ฐ์ฒด๋ฅผ ์ ๋ฌํด์ผ ํฉ๋๋ค. ์ด ์ต์ ์ API ๋ฌธ์์์ ์์ธํ ํ์ธํ ์ ์์ต๋๋ค.
-
๋ ๋๋ง์ ์ํฅ์ ๋ฏธ์น์ง ์๋
<template>
ํ๊ทธ์ v-if ์ง์์๋ฅผ ์ ์ฉํจ์ผ๋ก์จ ์ฌ๋ฌ ์๋ฆฌ๋จผํธ๋ค์ ํ ๋ฒ์ ์กฐ๊ฑด๋ถ๋ก ๋ํ๋ผ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์๋์ ๊ฐ์ด ์ ํจํ ์ฌ์ฉ์์ธ ๊ฒฝ์ฐ์ ํํด์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๋ณด์ฌ์ค ์ ์์ต๋๋ค.<template v-if="condition"> <h1>Name</h1> <p>Address</p> <p>Contact Details</p> </template>
-
Vue๋ ๊ฐ๋ฅํ ํ ์๋ฆฌ๋จผํธ๋ฅผ ํจ์จ์ ์ผ๋ก ๋ ๋๋งํ๋ ค ํฉ๋๋ค. ๊ทธ๋์ ์๋ฆฌ๋จผํธ๋ฅผ ์ฒ์๋ถํฐ ๋ค์ ๋ง๋ค๊ธฐ๋ณด๋ค๋ ์ฌ์ฌ์ฉํ๋ ค ํฉ๋๋ค. ๊ทธ๋ฌ๋ ์ด๋ ๋ช ๊ฐ์ง ์ํฉ์์ ๋ฌธ์ ๋ฅผ ์ผ์ผํฌ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด,
input
์๋ฆฌ๋จผํธ๋ฅผv-if
์v-else
๋ธ๋ก ์์ชฝ์์ ์ฌ์ฉํ๋ฉด,input
์๋ฆฌ๋จผํธ๋ ์กฐ๊ฑด๋ฌธ์ ๋ฐ๋ผ ๋ฐ๋์ง ์๊ณ ์ต์ด์ ๋ ๋๋ง ๋ ์๋ฆฌ๋จผํธ์ ์ํ๋ฅผ ์ ์งํ๊ณ ์์ต๋๋ค.<template v-if="loginType === 'Admin'"> <label>Admin</label> <input placeholder="Enter your ID"> </template> <template v-else> <label>Guest</label> <input placeholder="Enter your name"> </template>
์ด ๊ฒฝ์ฐ์์
input
์๋ฆฌ๋จผํธ๋ ์ฌ์ฌ์ฉ๋์ด์๋ ์ ๋๊ธฐ ๋๋ฌธ์, key ์์ฑ์ ์ด์ฉํด ๋ ๊ฐ์input
์๋ฆฌ๋จผํธ๋ฅผ ๋ณ๊ฐ์ ๊ฒ์ผ๋ก ์ทจ๊ธํ๋๋ก ์ ์ธํ ์ ์์ต๋๋ค.<template v-if="loginType === 'Admin'"> <label>Admin</label> <input placeholder="Enter your ID" key="admin-id"> </template> <template v-else> <label>Guest</label> <input placeholder="Enter your name" key="user-name"> </template>
์์ ๊ฒฝ์ฐ๋ ๋ ๊ฐ์
input
์๋ฆฌ๋จผํธ๊ฐ ๋ณ๊ฐ์ ๊ฒ์ผ๋ก ์ทจ๊ธ๋๋ฉฐ ์๋ก์๊ฒ ์ด๋ค ์ํฅ๋ ๋ผ์น์ง ์์ต๋๋ค. -
v-for ์ง์์๋ v-if ๋ณด๋ค ๋ ๋์ ์ฐ์ ์์๋ฅผ ๊ฐ๊ณ ์๊ธฐ ๋๋ฌธ์, ํ ์๋ฆฌ๋จผํธ ๋ด์์ v-for์ v-if๋ฅผ ํจ๊ป ์ฐ๋ ๊ฒ์ ๊ถ์ฅ๋์ง ์์ต๋๋ค. ์ผ๋ฐ์ ์ผ๋ก ๋ค์๊ณผ ๊ฐ์ ์ด์ ๋๋ฌธ์ ๋ ์ง์์๋ฅผ ํจ๊ป ์ฐ๊ณค ํฉ๋๋ค.
- ๋ฆฌ์คํธ์ ์์๋ฅผ ํํฐ๋งํ๊ธฐ ์ํด ์๋ฅผ ๋ค์ด, v-if ์ง์์๋ฅผ ์ด์ฉํด ๋ฆฌ์คํธ์ ์๋ ์์ดํ ์ ํํฐ๋งํ๊ณ ์ถ์ ๊ฒฝ์ฐ์ ๋๋ค.
<ul> <li v-for="user in users" v-if="user.isActive" :key="user.id" > {{ user.name }} <li> </ul>
์ด ๊ฒฝ์ฐ๋ ์๋์ ๊ฐ์ด ์ฌ์ ์ computed ์์ฑ์ ์ด์ฉํด ํํฐ๋ง๋ ๋ฆฌ์คํธ๋ฅผ ๋ง๋ค์ด ์ฌ์ฉํ ์ ์์ต๋๋ค.
computed: { activeUsers: function () { return this.users.filter(function (user) { return user.isActive }) } } ...... // ...... // <ul> <li v-for="user in activeUsers" :key="user.id"> {{ user.name }} <li> </ul>
- ๋ฆฌ์คํธ ์์ฒด๊ฐ ์จ๊ฒจ์ ธ์ผ ํ ๋ ์๋ฅผ ๋ค์ด, v-if๋ฅผ ์ด์ฉํด ๋ฐ๋ณต๋๋ ๋ฆฌ์คํธ๋ฅผ ์จ๊ธฐ๊ณ ์ถ์ ๊ฒฝ์ฐ์ ๋๋ค.
<ul> <li v-for="user in users" v-if="shouldShowUsers" :key="user.id" > {{ user.name }} <li> </ul>
์ด ๊ฒฝ์ฐ๋ ์๋์ ๊ฐ์ด ์กฐ๊ฑด๋ฌธ์ ์์ ์๋ฆฌ๋จผํธ๋ก ์ฎ๊น์ผ๋ก์จ ํด๊ฒฐํ ์ ์์ต๋๋ค.
<ul v-if="shouldShowUsers"> <li v-for="user in users" :key="user.id" > {{ user.name }} <li> </ul>
-
Vue์์ ๊ฐ๋ณ DOM ๋ ธ๋๋ค์ ์ถ์ ํ๊ณ ๊ธฐ์กด ์๋ฆฌ๋จผํธ์ ์ฌ์ฌ์ฉ/์ฌ์ ๋ ฌ์ ์ํด, v-for์ ์์์ ๊ณ ์ ํ key ์์ฑ์ ์ ๊ณตํด์ผ ํฉ๋๋ค. key์ ๋ํ ์ด์์ ์ธ ๊ฐ์ ๊ฐ ํญ๋ชฉ์ ์๋ณํ ์ ์๋ ๊ณ ์ ํ ID์ ๋๋ค.
<div v-for="item in items" :key="item.id"> {{item.name}} </div>
๋ฐ๋ณต๋๋ DOM ๋ด์ฉ์ด ๋จ์ํ ๊ฒฝ์ฐ๋ ์๋์ ์ธ ์ฑ๋ฅ ํฅ์์ ์ํด ๊ธฐ๋ณธ ๋์์ ์์กดํ์ง ์๋ ๊ฒฝ์ฐ๋ฅผ ์ ์ธํ๋ฉด, ๊ฐ๋ฅํ๋ฉด ์ธ์ ๋ v-for์ key๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ด ์ข์ต๋๋ค. Note: ๊ฐ์ฒด๋ ๋ฐฐ์ด์ฒ๋ผ, ๊ธฐ๋ณธ ํ์ (Primitive value)์ด ์๋ ๊ฐ์ ํค๋ก ์ฌ์ฉํด์๋ ์๋ฉ๋๋ค. ๋ฌธ์์ด์ด๋ ์ซ์๋ฅผ ์ฌ์ฉํ์ธ์.
-
์ด๋ฆ์์ ๋ณผ ์ ์๋ฏ, ๋ฐฐ์ด์ ๋ณํ์ํค๋ ํจ์(mutation methods)๋ ์๋ณธ ๋ฐฐ์ด์ ๋ณ๊ฒฝ์ํต๋๋ค. ์๋์ ํจ์๋ ๋ทฐ(view) ์ ๋ฐ์ดํธ๋ฅผ ์ผ์ผํต๋๋ค.
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
์๋ฅผ ๋ค์ด, ์๋์ ๊ฐ์ด
todos
๋ฐฐ์ด์push
ํจ์๋ฅผ ์คํ์ํค๋ฉด ๋ทฐ ์ ๋ฐ์ดํธ๊ฐ ์ผ์ด๋ฉ๋๋ค.vm.todos.push({ message: 'Baz' })
-
๋ฐฐ์ด์ ๋์ฒดํ๋ ํจ์๋ ์๋ณธ ๋ฐฐ์ด์ ์์ ํ์ง ์๊ณ , ํญ์ ์๋ก์ด ๋ฐฐ์ด์ ๋ฐํํฉ๋๋ค. ์๋์ ํจ์๋ ๋ฐฐ์ด์ ๋์ฒดํ๋ ํจ์์ ๋๋ค.
- filter()
- concat()
- slice()
์๋ฅผ ๋ค์ด, ์๋์ ๊ฐ์ด
status
์์ฑ์ ๋ฐ๋ผtodos
๋ฐฐ์ด์ ํํฐ๋งํ ์๋ก์ด ๋ฐฐ์ด์ ๋ฐํ๋ฐ์ ์ ์์ต๋๋ค.vm.todos = vm.todos.filter(function (todo) { return todo.status.match(/Completed/) })
Vue๊ฐ DOM์ ํจ์จ์ ์ผ๋ก ์ฌ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์, ์ ์ฒด ๋ฆฌ์คํธ๊ฐ ์๋ก ๋ ๋๋ง๋์ง๋ ์์ต๋๋ค.
-
Vue๋ ์๋์ ๋ ๊ฐ์ง ๊ฒฝ์ฐ์ ๋ณ๊ฒฝ ์ฌํญ์ ๊ฐ์งํ ์ ์์ต๋๋ค.
- ์ธ๋ฑ์ค๋ก ๋ฐฐ์ด์ ์๋ ํญ๋ชฉ์ ์ง์ ํ ๋นํ๋ ๊ฒฝ์ฐ
vm.todos[indexOfTodo] = newTodo
- ๋ฐฐ์ด์ ๊ธธ์ด๋ฅผ ์์ ํ๋ ๊ฒฝ์ฐ
vm.todos.length = todosLength
์ด๋
set
๊ณผsplice
ํจ์๋ฅผ ์ด์ฉํด ํด๊ฒฐํ ์ ์์ต๋๋ค.์ฒซ ๋ฒ์งธ ๊ฒฝ์ฐ
// Vue.set Vue.set(vm.todos, indexOfTodo, newTodoValue)
// Array.prototype.splice vm.todos.splice(indexOfTodo, 1, newTodoValue)
๋ ๋ฒ์งธ ๊ฒฝ์ฐ
vm.todos.splice(todosLength)
-
Vue๋ ์ถ๊ฐ๋๊ฑฐ๋ ์ญ์ ๋ ์์ฑ์ ๋ฐ์ํ์ผ๋ก ์ ๊ทผํ ์ ์์ต๋๋ค.
var vm = new Vue({ data: { user: { name: 'John' } } }) // `vm.name` is now reactive vm.email = john@email.com // `vm.email` is NOT reactive
์ด ๊ฒฝ์ฐ๋
Vue.set(object, key, value)
๋Object.assign()
๋ฅผ ์ด์ฉํจ์ผ๋ก์จ ๋ฐ์ํ ์์ฑ์ ์ถ๊ฐํ ์ ์์ต๋๋ค.Vue.set(vm.user, 'email', john@email.com);
vm.user = Object.assign({}, vm.user, { email: john@email.com })
-
v-for
์ง์์์ ์ ์๋ฅผ ์ฌ์ฉํด ํน์ ํ์๋งํผ ๋ฐ๋ณตํด ๋ ๋๋ง ํ ์ ์์ต๋๋ค.<div> <span v-for="n in 20">{{ n }} </span> </div>
์ด ๊ฒฝ์ฐ 1๋ถํฐ 20๊น์ง ์ซ์๊ฐ ์ถ๋ ฅ๋ฉ๋๋ค.
-
<template>
์์ v-if๋ฅผ ์ฌ์ฉํ ๊ฒ๊ณผ ์ ์ฌํ๊ฒ,<template>
์์ v-for ๋ฌธ๋ฒ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.<ul> <template v-for="todo in todos"> <li>{{ todo.title }}</li> <li class="divider"></li> </template> </ul>
-
VueJS์์๋ ์์ ์๋ฐ์คํฌ๋ฆฝํธ์ ์ ์ฌํ๊ฒ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ํจ์์์
$event
๋ณ์๋ฅผ ํธ์ถํด ์ฌ์ฉํ ์ ์์ต๋๋ค.<button v-on:click="show('Welcome to VueJS world', $event)"> Submit </button> methods: { show: function (message, event) { // now we have access to the native event if (event) event.preventDefault() console.log(message); } }
-
์ผ๋ฐ์ ์ผ๋ก ์๋ฐ์คํฌ๋ฆฝํธ์์๋ ์ด๋ฒคํธ ํธ๋ค๋ฌ ๋ด๋ถ์์
event.preventDefault()
๋๋event.stopPropagation()
๋ฅผ ์ ๊ณตํฉ๋๋ค. Vue์ ๋ฉ์๋ ๋ด๋ถ์์๋ ์ด ์์ ์ ํ ์ ์์ง๋ง, DOM์์ ๋ฐ์ํ ์ด๋ฒคํธ์ ๋ฉ์๋์ ๋ก์ง์ ๋ณ๊ฐ๋ก ๊ตฌ๋ถํ๋ ๊ฒ์ด ์ข์ต๋๋ค.์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด, Vue๋
v-on
์ด๋ฒคํธ์ ์ด๋ฒคํธ ์์์ด๋ฅผ ์ ๊ณตํฉ๋๋ค. ์์์ด๋ ์ ์ผ๋ก ํ์๋ ์ ๋ฏธ์ฌ ์ ๋๋ค..stop
.prevent
.capture
.self
.once
.passive
.stop
์์์ด๋ฅผ ์๋ก ๋ค์ด๋ณด๊ฒ ์ต๋๋ค.<!-- the click event's propagation will be stopped --> <a v-on:click.stop="methodCall"></a>
์์์ด๋ ์ฐ์ํด์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
<!-- modifiers can be chained --> <a v-on:click.stop.prevent="doThat"></a>
-
Vue๋ ํค๋ณด๋ ์ด๋ฒคํธ๋ฅผ ์ ์ดํ๊ธฐ ์ํด
v-on
์ง์์์ ํค ์์์ด๋ฅผ ์ ๊ณตํฉ๋๋ค.<!-- only call `vm.show()` when the `keyCode` is 13 --> <input v-on:keyup.13="show">
๋ชจ๋ ํค ์ฝ๋๋ฅผ ์ธ์ฐ๋ ๊ฒ์ ์ด๋ ต๊ธฐ ๋๋ฌธ์, Vue์์๋ ์์ฃผ ์ฌ์ฉ๋๋ ํค๋ค์ ๋ณ์นญ์ ์ ๊ณตํ๊ณ ์์ต๋๋ค.
.enter
.tab
.delete
(โDeleteโ์ โBackspaceโ ํฌํจ).esc
.space
.up
.down
.left
.right
์ ์์์ ํค ์ฝ๋๋ ์๋์ ๊ฐ์ด ๋ณ์นญ์ผ๋ก ๋ค์ ์ธ ์ ์์ต๋๋ค.
<input v-on:keyup.enter="submit"> // (OR) <!-- with shorthand notation--> <input @keyup.enter="submit">
ํค ์ฝ๋ ์ด๋ฒคํธ์ ์ฌ์ฉ์ ์ต์ ๋ธ๋ผ์ฐ์ ์์๋ ์ง์๋์ง ์์ ์ ์์ต๋๋ค.
-
์ ์ญ
config.keyCodes
๊ฐ์ฒด๋ฅผ ํตํด ํค ์์์ด๋ฅผ ์ปค์คํฐ๋ง์ด์งํ ์ ์์ต๋๋ค. ์ฌ๊ธฐ์๋ ๋ช ๊ฐ์ง ๊ท์น๋ค์ด ์์ต๋๋ค.- ์นด๋ฉ ์ผ์ด์ค(camelCase)๋ฅผ ๋์ ์๋ฐ์ดํ๋ก ๊ฐ์ธ์ง ์ผ๋ฐฅ ์ผ์ด์ค(Kebab-case)๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
- ๋ฐฐ์ด์ ์ด์ฉํด ํ ๋ฒ์ ์ฌ๋ฌ ๊ฐ๋ค์ ์ ์ํ ์ ์์ต๋๋ค.
Vue.config.keyCodes = { f1: 112, "media-play-pause": 179, down: [40, 87] }
-
Vue์์๋ ๋ค์ ์์์ด๋ฅผ ์ฌ์ฉํด ํด๋น ์์์ด ํค๊ฐ ๋๋ฌ์ง ๊ฒฝ์ฐ์๋ง ๋ง์ฐ์ค ๋๋ ํค๋ณด๋ ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ํฌ ์ ์์ต๋๋ค.
.ctrl
.alt
.shift
.meta
์๋๋ ์ปจํธ๋กค ํค๊ฐ ๋๋ฆฐ ์ํ์์ ํด๋ฆญ ์ด๋ฒคํธ๋ฅผ ํ์ฑํ ํ๋ ์์์ ๋๋ค.
<!-- Ctrl + Click --> <div @click.ctrl="doSomething">Do something</div>
-
Vue๋ ํน์ ํ ๋ง์ฐ์ค ๋ฒํผ์ผ๋ก ๋ฐ์ํ ์ด๋ฒคํธ๋ฅผ ์ ์ดํ ์ ์์ต๋๋ค.
.left
.right
.middle
๋ง์ฐ์ค ์ด๋ฒคํธ๋ก
.right
๋ฅผ ์ด์ฉํ ์์์ ๋๋ค.<button v-if="button === 'right'" v-on:mousedown.right="increment" v-on:mousedown.left="decrement" />
-
v-model
์ง์์๋ฅผ ์ด์ฉํดinput
,textarea
,select
์๋ฆฌ๋จผํธ์ ๋ฐ์ดํฐ๋ฅผ ์๋ฐฉํฅ์ผ๋ก ์ ์ดํ ์ ์์ต๋๋ค. ์๋์input
์๋ฆฌ๋จผํธ๋ฅผ ์ดํด๋ณด์ธ์.<input v-model="message" placeholder="Enter input here"> <p>The message is: {{ message }}</p>
v-model
์ ๋ชจ๋form
์๋ฆฌ๋จผํธ์์ HTML ์์ฑ(attribute)์ผ๋ก ์ ์ธ๋value
,checked
๊ทธ๋ฆฌ๊ณselected
๋ฅผ ๋ฌด์ํฉ๋๋ค. ๊ทธ ๋์ Vue ์ธ์คํด์ค์์v-model
๋ก ๋ฐ์ธ๋ฉํ ๊ฐ์ ์ด์ฉํฉ๋๋ค. ๋ฐ๋ผ์ ์ปดํฌ๋ํธ์ data์์ ์ด๊ธฐ๊ฐ์ ์ ์ธํด์ผ ํฉ๋๋ค. -
v-model
์ง์์์๋ ์ธ ๊ฐ์ง ์์์ด๊ฐ ์ง์๋ฉ๋๋ค.1. lazy: ๊ธฐ๋ณธ์ ์ผ๋ก,
v-model
์ ํ๋์ ํค ์ ๋ ฅ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ๋๋ง๋ค data๊ฐ ์ ๋ฐ์ดํธ๋ฉ๋๋ค. ์ด๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด์๋.lazy
์์์ด๋ฅผ ์ด์ฉํฉ๋๋ค.<!-- synced after "change" instead of "input" --> <input v-model.lazy="msg" >
2. number:
v-model
์.number
์์์ด๋ฅผ ๋ถ์ด๋ฉด ์๋์ ์ผ๋ก ์ฌ์ฉ์์ ์ ๋ ฅ์ ์๋ฃํ์ดNumber
๋ก ๋ณํ๋ฉ๋๋ค. HTMLinput
ํ๊ทธ์ ์์ฑ์ดtype="number"
์ผ์ง๋ผ๋ ๋ฐํ๋๋ ๊ฐ์ ์๋ฃํ์ ๋ฌธ์์ด์ด๊ธฐ ๋๋ฌธ์, ์ซ์ ์๋ฃํ์ด ํ์ํ๋ค๋ฉด.number
์์์ด๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค.<input v-model.number="age" type="number">
3. trim:
.trim
์์์ด๋ฅผ ์ฌ์ฉ์ ์ ๋ ฅ์์ ์ฒ์๊ณผ ๋์ ๋ค์ด์๋ ๊ณต๋ฐฑ์ ์๋์ผ๋ก ์ ๊ฑฐํด์ค๋๋ค.<input v-model.trim="msg">
-
์ปดํฌ๋ํธ๋ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ๋ฉด์ ์ด๋ฆ์ด ๋ช ๋ช ๋ Vue ์ธ์คํด์ค์ ๋๋ค. ์ปดํฌ๋ํธ๋ Vue์ฒ๋ผ data, computed, watch, methods, ๋ผ์ดํ์ฌ์ดํด ์ต์ ์ ๊ฐ๊ณ ์์ต๋๋ค. ์๋๋ Vue์ ์ ์ญ์ผ๋ก ์ปดํฌ๋ํธ๋ฅผ ์ถ๊ฐํ๋ ์์์ ๋๋ค.
// Define a new component called button-counter Vue.component('button-counter', { template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>' data: function () { return { count: 0 } }, })
์ด ์ปดํฌ๋ํธ๋ ์ ์ญ์ผ๋ก ์ ์ธ๋์๊ธฐ ๋๋ฌธ์ Vue ์ธ์คํด์ค์์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
<div id="app"> <button-counter></button-counter> </div> var vm = new Vue({ el: '#app' });
-
props๋ ์์ ์ปดํฌ๋ํธ์ ์ ๋ณด๋ฅผ ํ์ ์ปดํฌ๋ํธ๋ก ์ ๋ฌํ ์ ์๋ ์ฌ์ฉ์ ์ง์ ์ ์์ฑ์ ๋๋ค. ์์ ์ปดํฌ๋ํธ์์ ์ ๋ฌ๋๋ props๋ ํ์ ์ปดํฌ๋ํธ์ ์์ฑ์ผ๋ก ์ฌ๊ฒจ์ง๋ฉฐ, ํ์ ์ปดํฌ๋ํธ์์๋ props ์ต์ ์ ์ฌ์ฉํ์ฌ ์์ ํ ๊ฒ์ผ๋ก ์์๋๋ props๋ฅผ ๋ช ์์ ์ผ๋ก ์ ์ธํด์ผ ํฉ๋๋ค.
Vue.component('todo-item', { props: ['title'], template: '<h2>{{ title }}</h2>' })
ํ์ ์ปดํฌ๋ํธ์์ props๊ฐ ๋ฑ๋ก๋๊ณ ๋๋ฉด, ์์ ์ปดํฌ๋ํธ์์๋ ์ฌ์ฉ์ ์ง์ ์์ฑ์ ์ด์ฉํด ๊ฐ์ ์ ๋ฌํ ์ ์์ต๋๋ค.
<todo-item title="Learn Vue conceptsnfirst"></todo-item>
-
ํ ํ๋ฆฟ์ด ์ฌ๋ฌ ๊ฐ์ ์๋ฆฌ๋จผํธ๋ค๋ก ๊ตฌ์ฑ๋์ด ์์ ๋, ์ปดํฌ๋ํธ์ ์ต์๋จ ํ ํ๋ฆฟ์ ๋ฐ๋์ ๋จ์ผ ์๋ฆฌ๋จผํธ๋ก ๊ฐ์ธ์ ธ ์์ด์ผ ํฉ๋๋ค.
<div class="todo-item"> <h2>{{ title }}</h2> <div v-html="content"></div> </div>
๊ทธ๋ ์ง ์๋ค๋ฉด,
"Component template should contain exactly one root element..."
๋ผ๋ ์๋ฌ๋ฅผ ๋ฐ์์ํต๋๋ค. -
ํ์ ์ปดํฌ๋ํธ์์
$event
๊ฐ์ฒด๋ฅผ ์ด์ฉํด ์์ ์ปดํฌ๋ํธ๋ก ์ด๋ฒคํธ๋ฅผ ๋ฐ์ํฌ ์ ์์ต๋๋ค.Vue.component('todo-tem', { props: ['todo'], template: ` <div class="todo-item"> <h3>{{ todo.title }}</h3> <button v-on:click="$emit('increment-count', 1)"> Add </button> <div v-html="todo.description"></div> </div> ` })
์ด ๋ ์์ ์ปดํฌ๋ํธ์์๋
v-on
์ง์์๋ฅผ ์ด์ฉํด ํ์ ์ปดํฌ๋ํธ์์ ๋ช ๋ช ํ ์ด๋ฒคํธ์ ๊ฐ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.<ul v-for="todo in todos"> <li> <todo-item v-bind:key="todo.id" v-bind:todo="todo" v-on:increment-count="total += 1"> </todo-item> </li> </ul> <span> Total todos count is {{total}}</span>
-
์ฌ์ฉ์ ์ ์ input ์ปดํฌ๋ํธ์์๋
v-model
์ ํ์ฉํ ์ ์์ต๋๋ค. ํด๋น ์ปดํฌ๋ํธ์input
์ ์๋ ๊ท์น๋ค์ ์ค์ํด์ผ ํฉ๋๋ค.input
์value
๋ฅผ props๋ฅผ ์ด์ฉํด ๋ฐ์ธ๋ฉํฉ๋๋ค.- ์๋ก์ด ๊ฐ์ด ์
๋ ฅ๋๋
input
์ด๋ฒคํธ ๋ฐ์ ์, ํด๋น ๊ฐ์emit
ํ์ฌ ์์ ์ปดํฌ๋ํธ๋ก ์ด๋ฒคํธ๋ฅผ ์ ๋ฌํฉ๋๋ค.
Vue.component('custom-input', { props: ['value'], template: ` <input v-bind:value="value" v-on:input="$emit('input', $event.target.value)" > ` })
์ด ๊ฒฝ์ฐ ์์ ์ปดํฌ๋ํธ์์
v-model
์ ์ด์ฉํด ๊ฐ์ ๋ฐ์ธ๋ฉํ ์ ์์ต๋๋ค.<custom-input v-model="searchInput"></custom-input>
-
Vue์์๋
<slot>
์ ์ด์ฉํด ์์ ์ปดํฌ๋ํธ์์ ํ์ ์ปดํฌ๋ํธ ๋ด๋ถ์ ์ฌ์ฉ์ ์ ์์ ์ปจํ ์ธ ๋ฅผ ์ง์ด ๋ฃ์ ์ ์์ต๋๋ค. ํ์ ์ปดํฌ๋ํธ์<slot>
์ ์ด์ฉํด ๋ฌธ๊ตฌ๋ฅผ ๋์ ์ผ๋ก ๋ฃ์ ์ ์๋ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค์ด๋ด ์๋ค.Vue.component('alert', { template: ` <div class="alert-box"> <strong>Error!</strong> <slot></slot> </div> ` })
<alert>
ํ๊ทธ ์์ ๋ฃ์ ๊ฐ์ ์ปดํฌ๋ํธ ๋ด๋ถ์<slot>
์ ์ปจํ ์ธ ๋ก ๋ค์ด๊ฐ๊ฒ ๋ฉ๋๋ค.<alert> There is an issue with in application. </alert>
-
์ปดํฌ๋ํธ๋ฅผ ์ ์ญ์ผ๋ก ๋ฑ๋กํ๊ฒ ๋๋ฉด ๋ชจ๋ Vue ์ธ์คํด์ค์์ ํด๋น ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ปดํฌ๋ํธ๋
Vue.component()
ํจ์๋ฅผ ์ด์ฉํด ์ ์ญ ๋ฑ๋กํ ์ ์์ต๋๋ค.Vue.component('my-component-name', { // ... options ... })
Vue ์ธ์คํด์ค์ ์ฌ๋ฌ ๊ฐ์ ์ปดํฌ๋ํธ๋ฅผ ์ ์ญ ๋ฑ๋กํด๋ด ์๋ค.
Vue.component('component-a', { /* ... */ }) Vue.component('component-b', { /* ... */ }) Vue.component('component-c', { /* ... */ }) new Vue({ el: '#app' })
์์ ์ปดํฌ๋ํธ๋ค์ Vue ์ธ์คํด์ค ๋ด์์ ์ฌ์ฉ๋ ์ ์์ต๋๋ค.
<div id="app"> <component-a></component-a> <component-b></component-b> <component-c></component-c> </div>
์ ์ญ์ผ๋ก ๋ฑ๋กํ ์ปดํฌ๋ํธ๋ค์ ํ์ ์ปดํฌ๋ํธ์์๋ ์ฌ์ฉ์ด ๊ฐ๋ฅํฉ๋๋ค.
-
์ ์ญ ๋ฑ๋ก์ผ๋ก ์ธํด ์ฌ์ฉ๋์ง ์๋ ์ปดํฌ๋ํธ๊ฐ ๋น๋ ์์ ์ฌ์ ํ ๋จ์์์ ์ ์์ต๋๋ค. ์ด๋ ๋ถํ์ํ ์๋ฐ์คํฌ๋ฆฝํธ๋ฅผ ๋ง๋ค์ฃ . ์ด๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด, ์๋์ ๊ฐ์ด ์ปดํฌ๋ํธ๋ฅผ ์ง์ญ ๋ฑ๋กํ ์ ์์ต๋๋ค.
- ์ฐ์ ์๋ฐ์คํฌ๋ฆฝํธ ๊ฐ์ฒด๋ก ์ปดํฌ๋ํธ๋ฅผ ์ ์ํฉ๋๋ค.
var ComponentA = { /* ... */ } var ComponentB = { /* ... */ } var ComponentC = { /* ... */ }
์ง์ญ ๋ฑ๋กํ ์ปดํฌ๋ํธ๋ ๋ค๋ฅธ ์ปดํฌ๋ํธ์ ํ์์์๋ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด ๊ฒฝ์ฐ,
components
์์ฑ์ผ๋ก ์ปดํฌ๋ํธ๋ฅผ ์ถ๊ฐํด ์ฌ์ฉํ ์ ์์ต๋๋ค.var ComponentA = { /* ... */ } var ComponentB = { components: { 'component-a': ComponentA }, // ... }
- Vue ์ธ์คํด์ค์์
components
์์ฑ์ ์ฌ์ฉํ ์ปดํฌ๋ํธ๋ค์ ์ ์ํ ์ ์์ต๋๋ค.
new Vue({ el: '#app', components: { 'component-a': ComponentA, 'component-b': ComponentB } })
-
์ง์ญ ๋ฑ๋ก์ ๊ฒฝ์ฐ, ๊ฐ ์ปดํฌ๋ํธ๋ฅผ ๋๋ ํ ๋ฆฌ์ ์์ฑํ๊ณ ๊ฐ๊ฐ์ ์ปดํฌ๋ํธ๋ ๋ค๋ฅธ ์ปดํฌ๋ํธ ์์์
import
ํ์ฌ ์ฌ์ฉํ๋ ๊ฒ์ด ๊ถ์ฅ๋ฉ๋๋ค. ๋ง์ฝ ์ฌ๋ฌ๋ถ๋ค์ด ์ปดํฌ๋ํธ C์์ ์ปดํฌ๋ํธ A์ B๋ฅผ ์ฌ์ฉํ๊ณ ์ถ๋ค๋ฉด ์๋์ ๊ฐ์ ์ค์ ์ ํด์ผ ํฉ๋๋ค.import ComponentA from './ComponentA' import ComponentB from './ComponentC' export default { components: { ComponentA, ComponentB } }
์์ ๊ฒฝ์ฐ ์ปดํฌ๋ํธ A์ ์ปดํฌ๋ํธ B๋ ์ปดํฌ๋ํธ C์ ํ ํ๋ฆฟ์์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์ ์ญ ๋ฑ๋ก์ ๊ฒฝ์ฐ, ๊ณตํต์ ์ผ๋ก ์ฌ์ฉ๋๋ ์ปดํฌ๋ํธ๋ฅผ ๊ฐ๊ฐ์ ํ์ผ์์
export
ํด์ผํฉ๋๋ค. ํ์ง๋งwebpack
๊ณผ ๊ฐ์ ์ ๋ช ํ ๋ฒ๋ค๋ฌ๋ค์require.context
๋ผ๋ ๋ฌธ๋ฒ์ ์ด์ฉํด์ ์ปดํฌ๋ํธ๋ฅผ ์ฝ๊ฒ ์ ์ญ์ ์ผ๋ก ๋ฑ๋กํ ์ ์๊ฒ ํด์ค๋๋ค.import Vue from 'vue' import upperFirst from 'lodash/upperFirst' import camelCase from 'lodash/camelCase' const requireComponent = require.context( // The relative path of the components folder './components', // Whether or not to look in subfolders false, // The regular expression used to match base component filenames /Base[A-Z]\w+\.(vue|js)$/ ) requireComponent.keys().forEach(fileName => { // Get component config const componentConfig = requireComponent(fileName) // Get PascalCase name of component const componentName = upperFirst( camelCase( // Strip the leading `./` and extension from the filename fileName.replace(/^\.\/(.*)\.\w+$/, '$1') ) ) // Register component globally Vue.component( componentName, // Look for the component options on `.default`, which will // exist if the component was exported with `export default`, // otherwise fall back to module's root. componentConfig.default || componentConfig ) })
-
props
์๋ ํ์ ์ ์ง์ ํ ์๋, ์ง์ ํ์ง ์์ ์๋ ์์ต๋๋ค. ํ์ง๋ง ์ผ๋ฐ์ ์ผ๋ก ํ์ ์ ์ง์ ํ๋ฉด ๋ค๋ฅธ ๊ฐ๋ฐ์๋ค์ด ํด๋น ์ฝ๋์์ ์๋ชป๋ ํ์ ์props
๋ฅผ ๋๊ฒจ์ฃผ๋ ์ค์๋ฅผ ์ค์ฌ์ฃผ๊ธฐ ๋๋ฌธ์, ๊ฐ๋ฅํ๋ฉด ํ์ ์ ์ง์ ํด์ฃผ๋ ๊ฒ์ด ์ข์ต๋๋ค.props: { name: String, age: Number, isAuthenticated: Boolean, phoneNumbers: Array, address: Object }
props
๊ฐ์ฒด์ ์์ฑ๊ณผ ๊ฐ์ ์ ์ธํจ์ผ๋ก์, ํ์ ์ ์ ์ธํ ์ ์์ต๋๋ค. -
๋ชจ๋
props
๋ ํ์ ์์ฑ๊ณผ ์์ ์์ฑ ์ฌ์ด์์ ๋จ๋ฐฉํฅ ๋ฐ์ธ๋ฉ์ ํ์ฑํฉ๋๋ค. ์ฆ, ์์ ์์ฑ์ด ๋ณ๊ฒฝ๋๋ ๊ฒ์ ํ์ ์์ฑ์๊ฒ ์ ๋ฌ๋์ง๋ง, ๊ทธ ๋ฐ๋๋ ์๋ฉ๋๋ค. ์์น์ ์ผ๋ก, ํ์ ์ปดํฌ๋ํธ์์๋ ์์ ์ปดํฌ๋ํธ์์ ๋ฐ์props
์ ์์ ํด์๋ ์๋ฉ๋๋ค.ํ์ ์ปดํฌ๋ํธ์์
props
์์ ์ ํ์์ฑ์ ๋๋ ์ ์๋ ๋ช ๊ฐ์ง ๊ฒฝ์ฐ๊ฐ ์๋๋ฐ, ์๋์ ๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก ํด๊ฒฐํ ์ ์์ต๋๋ค.- ์์ ์ปดํฌ๋ํธ์
props
๋ ํ์ ์ปดํฌ๋ํธ์ ์ด๊ธฐ๊ฐ ์ค์ ์๋ง ์ฌ์ฉ๋๊ณ ๊ทธ ์ดํ์๋ ๋ก์ปฌ ๋ฐ์ดํฐ ์์ฑ์ผ๋ก ํ์ฉ๋๋ ๊ฒฝ์ฐ:
์ด ๊ฒฝ์ฐ, ํ์ ์ปดํฌ๋ํธ์์ ์ฌ์ฉํ ์์ฑ์
data
์ ์ ์ธํ๊ณ , ๊ทธ ๊ฐ์props
๋ก ์ด๊ธฐํํ๋ฉด ๋ฉ๋๋ค.props: ['defaultUser'], data: function () { return { username: this.defaultUser } }
- ์์ ์ปดํฌ๋ํธ์์
props
๋ก ์ ํด์ฃผ๋ ๊ฐ์ด ์์ ๋๋ ๊ฒฝ์ฐ
์ด ๊ฒฝ์ฐ, ํ์ ์ปดํฌ๋ํธ์์
computed
์์ฑ์ ์ด์ฉํดprops
์ ๊ฐ์ด ๋ฐ๋ ๋๋ง๋ค ์ ๊ท ๊ฐ์ ์ป์ ์ ์์ต๋๋ค.props: ['environment'], computed: { localEnvironment: function () { return this.environment.trim().toUpperCase() } }
- ์์ ์ปดํฌ๋ํธ์
-
props
๊ฐ ์๋ ์์ฑ์ด๋, ์ปดํฌ๋ํธ์ ์ ๋ฌ๋๊ธฐ๋ ํ์ง๋ง ํด๋นprops
๊ฐ ํ์ ์ปดํฌ๋ํธ์์ ์ ์๋์ง๋ ์์ ์์ฑ์ ๋งํฉ๋๋ค. ๋ง์ฝdata-tooltip
์์ฑ์ ์๊ตฌํ๋ ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ๊ณ ์๋ค๊ณ ๊ฐ์ ํด๋ด ์๋ค. ์ด ์์ฑ์ ์ปดํฌ๋ํธ ์ธ์คํด์ค์ ๋ค์๊ณผ ๊ฐ์ด ์ถ๊ฐ ํ ์ ์์ต๋๋ค.<custom-input data-tooltip="Enter your input" />
์์ ์ปดํฌ๋ํธ์์๋ถํฐ
props
๊ฐ ์๋ ์์ฑ์ ๋๊ฒจ์ฃผ๋ ค ํ๋ค๋ฉด, ํ์ ์ปดํฌ๋ํธ์์ ๊ฐ์ ์ด๋ฆ์ ๊ฐ์ง ์์ฑ์ ๋ฎ์ด์์์ง๋๋ค. ํ์ง๋งclass
๋style
๊ฐ์props
๋ ์์ธ๋ก, ์ด ๊ฐ๋ค์ ํ์ ์ปดํฌ๋ํธ์ ํฉ์ณ์ง๋๋ค.//Parent component <custom-input class="custom-class" /> //Child component <input type="date" class="date-control">
-
Vue์์๋ ํ์ , ํ์ ์ฌ๋ถ, ๋ํดํธ ๊ฐ ๋ฑ
props
์ ์ ํจ์ฑ ๊ฒ์ฆ์ ์ ๊ณตํ๊ณ ์์ต๋๋ค. ์๋์ ๊ฐ์ดprops
๋ฅผ ๊ฒ์ฆํ๋ ๊ท์น์ด ์์ฑ์ผ๋ก ๋ด๊ธด ๊ฐ์ฒด๋ฅผ ์ ๊ณตํ๋ฉด ๋ฉ๋๋ค.Vue.component('user-profile', { props: { // Basic type check (`null` matches any type) age: Number, // Multiple possible types identityNumber: [String, Number], // Required string email: { type: String, required: true }, // Number with a default value minBalance: { type: Number, default: 10000 }, // Object with a default value message: { type: Object, // Object or array defaults must be returned from // a factory function default: function () { return { message: 'Welcome to Vue' } } }, // Custom validator function location: { validator: function (value) { // The value must match one of these strings return ['India', 'Singapore', 'Australia'].indexOf(value) !== -1 } } } })
-
์ผ๋ฐ์ ์ธ ์ปดํฌ๋ํธ์์
v-model
์ง์์๋ value๋ฅผprops
๋ก ์ฌ์ฉํ๊ณ input์ ์ด๋ฒคํธ๋ก ์ฌ์ฉํ์ง๋ง, ์ฒดํฌ ๋ฐ์ค๋ ๋ผ๋์ค ๋ฒํผ๊ฐ์ ์ผ๋ถ ์ ๋ ฅ ํ์ ์ ๋ค๋ฅธ ๋ชฉ์ ์ผ๋กvalue
์์ฑ์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด๋ฐ ๊ฒฝ์ฐ์๋v-model
์ ์ปค์คํฐ๋ง์ด์งํด์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค.Vue.component('custom-checkbox', { model: { prop: 'checked', event: 'change' }, props: { checked: Boolean }, template: ` <input type="checkbox" v-bind:checked="checked" v-on:change="$emit('change', $event.target.checked)" > ` })
์ด ์ปดํฌ๋ํธ์์
v-model
์ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉํ ์ ์์ต๋๋ค.<custom-checkbox v-model="selectFramework"></custom-checkbox>
selectFramework
์์ฑ์props
์คchecked
๋ก ๋์ด๊ฐ ๊ฒ์ด๊ณ , ์ฒดํฌ ๋ฐ์ค ์ปดํฌ๋ํธ์์ ๊ฐ์ด ๋ณ๊ฒฝ๋๋ฉด ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ํฌ ๊ฒ์ ๋๋ค. -
Vue์์๋ ํญ๋ชฉ๋ค์ด DOM์์ ์ถ๊ฐ, ๊ฐฑ์ ๋๋ ์ญ์ ๋ ๋, ๋ค์ํ ๋ฐฉ๋ฒ์ผ๋ก ํธ๋์ง์ ํจ๊ณผ๋ฅผ ์ ํ ์ ์์ต๋๋ค.
- CSS ํธ๋์ง์ ๊ณผ ์ ๋๋ฉ์ด์ ์ ์ํ ํด๋์ค๋ฅผ ์๋์ผ๋ก ์ ์ฉ
- Animate.css์ ๊ฐ์ ์จ๋ํํฐ CSS ์ ๋๋ฉ์ด์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํตํฉ
- ํธ๋์ง์ ํ ์ค์ JavaScript๋ฅผ ์ฌ์ฉํ์ฌ DOM์ ์ง์ ์กฐ์
- Velocity.js์ ๊ฐ์ ์จ๋ํํฐ JavaScript ์ ๋๋ฉ์ด์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํตํฉ
-
Vue Router๋ Vue์์ ๋์ํ๋ ๊ณต์์ ์ธ ๋ผ์ฐํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค.
- ์ค์ฒฉ๋ ๋ผ์ฐํธ/๋ทฐ ๋งคํ
- ๋ชจ๋ํ๋, ์ปดํฌ๋ํธ ๊ธฐ๋ฐ์ ๋ผ์ฐํฐ ์ค์
- ๋ผ์ฐํฐ ํ๋ผ๋ฏธํฐ, ์ฟผ๋ฆฌ, ์์ผ๋์นด๋
- Vue์ ํธ๋์ง์ ์์คํ ์ ์ด์ฉํ ํธ๋์ง์ ํจ๊ณผ
- ์ธ๋ฐํ ๋ค๋น๊ฒ์ด์ ์ปจํธ๋กค
- active CSS ํด๋์ค๋ฅผ ์๋์ผ๋ก ์ถ๊ฐํด์ฃผ๋ ๋งํฌ
- HTML5 ํ์คํ ๋ฆฌ ๋ชจ๋ ๋๋ ํด์ ๋ชจ๋(IE9์์ ์๋์ผ๋ก ํด๋ฐฑ)
- ์ฌ์ฉ์ ์ ์ ๊ฐ๋ฅํ ์คํฌ๋กค ๋์
-
Vue๋ฅผ ์ฌ์ฉํ๊ณ ์๋ค๋ฉด, ์ฝ๊ฒ Vue Router๋ฅผ ํตํฉํ ์ ์์ต๋๋ค.
Step 1: ๋จผ์ ํ ํ๋ฆฟ์์
<router-link>
ํ๊ทธ๋ฅผ ์ค์ ํฉ๋๋ค.<script src="https://unpkg.com/vue/dist/vue.js"></script> <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script> <div id="app"> <h1>Welcome to Vue routing app!</h1> <p> <!-- use router-link component for navigation using `to` prop. It rendered as an `<a>` tag --> <router-link to="/home">Home</router-link> <router-link to="/services">Services</router-link> </p> <!-- route outlet in which component matched by the route will render here --> <router-view></router-view> </div>
Step 2:
main.js
์์ Vue์ Vue ๋ผ์ฐํฐ๋ฅผimport
ํ๊ณVue.use()
ํจ์๋ฅผ ์ด์ฉํด ํธ์ถํฉ๋๋ค.import Vue from 'vue'; import VueRouter from 'vue-router'; Vue.use(VueRouter)
Step 3: ๋ผ์ฐํธ ์ปดํฌ๋ํธ๋ฅผ ์ ์ํ๊ฑฐ๋
import
ํฉ๋๋ค.const Home = { template: '<div>Home</div>' } const Services = { template: '<div>Services</div>' }
Step 4: ๋ผ์ฐํธ๋ฅผ ์ ์ํฉ๋๋ค. ๊ฐ ๋ผ์ฐํธ๋ ๋ฐ๋์ ์ปดํฌ๋ํธ์ ๋งคํ๋์ด์ผ ํฉ๋๋ค.
const routes = [ { path: '/home', component: Home }, { path: '/services', component: Services } ]
Step 5:
routes
์ต์ ๊ณผ ํจ๊ป router ์ธ์คํด์ค๋ฅผ ๋ง๋ญ๋๋ค.const router = new VueRouter({ routes // short for `routes: routes` })
Step 6: ๋ฃจํธ Vue ์ธ์คํด์ค๋ฅผ ๋ง๋ค๊ณ
mount
ํฉ๋๋ค.const app = new Vue({ router }).$mount('#app')
์ด์ Vue ์ดํ๋ฆฌ์ผ์ด์ ์์ ๋ค๋ฅธ ํ์ด์ง(Home, Services)๋ก ๋ค๋น๊ฒ์ดํธ ํ ์ ์์ต๋๋ค.
-
์ฃผ์ด์ง ํจํด์ ๊ฐ์ง ๋ผ์ฐํธ๋ฅผ ๋์ผํ ์ปดํฌ๋ํธ์ ๋งคํํด์ผํ๋ ๊ฒฝ์ฐ๊ฐ ์์ฃผ ์์ต๋๋ค. ๋์ ์ธ๊ทธ๋จผํธ๋ฅผ ์ด์ฉํด
/user/john/post/123
๋/user/jack/post/235
์ ๊ฐ์ด ๋งคํ๋ URL์ ๊ฐ์ง๋ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค์ด๋ด ์๋ค.const User = { template: '<div>User {{ $route.params.name }}, PostId: {{ route.params.postid }}</div>' } const router = new VueRouter({ routes: [ // dynamic segments start with a colon { path: '/user/:name/post/:postid', component: User } ] })
-
๋งค๊ฐ ๋ณ์์ ํจ๊ป ๋ผ์ฐํธ๋ฅผ ์ฌ์ฉํ ๋ ์ฃผ์ ํด์ผํ ์ ์ ์ฌ์ฉ์๊ฐ
/user/foo
์์/user/bar
๋ก ์ด๋ํ ๋ ๋์ผํ ์ปดํฌ๋ํธ ์ธ์คํด์ค๊ฐ ์ฌ์ฌ์ฉ๋๋ค๋ ๊ฒ์ ๋๋ค. ๋ ๋ผ์ฐํธ ๋ชจ๋ ๋์ผํ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํ๋ฏ๋ก ์ด์ ์ธ์คํด์ค๋ฅผ ์ญ์ ํ ๋ค์ ์ ์ธ์คํด์ค๋ฅผ ๋ง๋๋ ๊ฒ๋ณด๋ค ํจ์จ์ ์ ๋๋ค. ๊ทธ๋ฌ๋ ์ด๋ ๋ํ ์ปดํฌ๋ํธ์ ๋ผ์ดํ ์ฌ์ดํด ํ ์ด ํธ์ถ๋์ง ์์์ ์๋ฏธํฉ๋๋ค.๋์ผํ ์ปดํฌ๋ํธ์
params
๋ณ๊ฒฝ ์ฌํญ์ ๋ฐ์ํ๋ ค๋ฉด$route
๊ฐ์ฒด๋ฅผ ๋ณด๋ฉด๋ฉ๋๋ค.watch
์์$route
๊ด์ฐฐํ๊ธฐ:
const User = { template: '<div>User {{ $route.params.name }} </div>', watch: { '$route' (to, from) { // react to route changes... } } }
beforeRouteUpdate
๋ค๋น๊ฒ์ด์ ๊ฐ๋๋ฅผ ์ฌ์ฉํ๊ธฐ:
const User = { template: '<div>User {{ $route.params.name }} </div>', beforeRouteUpdate (to, from, next) { // react to route changes and then call next() } }
beforeRouteEnter
๊ฐ๋์์๋this
์ ์ ๊ทผํ ๊ถํ์ด ์๋ค๋ ๊ฒ์ ๊ธฐ์ตํ์ธ์. ๋์ ,next
์ฝ๋ฐฑ ํจ์๋ฅผ ์ด์ฉํด ์ธ์คํด์ค์ ์ ๊ทผํ ์ ์์ต๋๋ค. -
๋์ผํ URL์ด ์ฌ๋ฌ ๋ผ์ฐํธ์ ์ผ์นํ๋ ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค. ์ด ๊ฒฝ์ฐ ์ผ์นํ๋ ์ฐ์ ์์๋ ๋ผ์ฐํธ ์ ์์ ์์์ ๋ฐ๋ผ ๊ฒฐ์ ๋ฉ๋๋ค. ์ฆ, ๊ฒฝ๋ก๊ฐ ๋ ๋จผ์ ์ ์ ๋ ์๋ก ์ฐ์ ์์๊ฐ ๋์์ง๋๋ค.
const router = new VueRouter({ routes: [ // dynamic segments start with a colon { path: '/user/:name', component: User } // This route gets higher priority { path: '/user/:name', component: Admin } { path: '/user/:name', component: Customer } ] })
-
์ผ๋ฐ์ ์ผ๋ก ์ดํ๋ฆฌ์ผ์ด์ ์ ์ฌ๋ฌ ๋จ๊ณ์ ์ค์ฒฉ๋ ์ปดํฌ๋ํธ๋ก ์ด๋ฃจ์ด์ ธ ์์ต๋๋ค. URL์ ์ธ๊ทธ๋จผํธ ์ญ์ ์ค์ฒฉ๋ ์ปดํฌ๋ํธ์ ํน์ ๊ตฌ์กฐ์ ์ผ์นํฉ๋๋ค. ์ค์ฒฉ๋ ์์๋ ์์ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํ๋ ค๋ฉด
VueRouter
์์ฑ์์์config
๋กchildren
์ ์ค์ ํด์ผ ํฉ๋๋ค.ํ๋กํ๊ณผ ํฌ์คํธ๋ค์ด ์๋์ ์ธ ๊ฒฝ๋ก๋ก ์ค์ ๋ ์ดํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค์ด๋ด ์๋ค. ๋งค์นญ๋๋ ํ์ ๋ผ์ฐํธ๊ฐ ์์ ๊ฒฝ์ฐ์ ๋ ๋๋ง๋๋ ๋ผ์ฐํธ ์ปดํฌ๋ํธ๋ฅผ ์ค์ ํ ์ ์์ต๋๋ค.
const router = new VueRouter({ routes: [ { path: '/user/:id', component: User, children: [ { // UserProfile will be rendered inside User's <router-view> when /user/:id/profile is matched path: 'profile', component: UserProfile }, { // UserPosts will be rendered inside User's <router-view> when /user/:id/posts is matched path: 'posts', component: UserPosts }, // UserHome will be rendered inside User's <router-view> when /user/:id is matched { path: '', component: UserHome }, ] } ] })
-
์๋ง ํ๋์ ํ์ด์ง์์ HTML, CSS, JavaScript์ ๋ค๋ฅธ ํ์ผ๋ก ๋ถ๋ฆฌํด ๊ด๋ฆฌํด๋ณธ ๊ฒฝํ์ด ์์ ๊ฒ์ ๋๋ค. ํ์ง๋ง ์ฑ๊ธ ํ์ผ ์ปดํฌ๋ํธ์์๋ ํ ํ๋ฆฟ๊ณผ ์คํ์ผ, ๋ก์ง๋ค์ ํ๋์ ํ์ผ์ ์ ๋ฆฌํฉ๋๋ค.
<template> <div> <h1>Welcome {{ name }}!</h1> </div> </template> <script> module.exports = { data: function() { return { name: 'John' } } } </script> <style scoped> h1 { color: #34c779; padding: 3px; } </style>
-
์ฃผ๋ชฉํด์ผ ํ ์ค์ํ ์ ์ ๊ด์ฌ์ฌ ๋ถ๋ฆฌ๊ฐ ํ์ผ ํ์ ๋ถ๋ฆฌ์ ๊ฐ์ง ์๋ค๋ ๊ฒ์ ๋๋ค. ํ๋์ ์ธ UI ๊ฐ๋ฐ์์ ์ฝ๋๋ฒ ์ด์ค๋ฅผ ์๋ก ์ฝํ์๋ ์ธ ๊ฐ์ ๊ฑฐ๋ํ ๋ ์ด์ด๋ก ๋๋๋ ๋์ , ๋์จํ๊ฒ ๊ฒฐํฉ ๋ ์ปดํฌ๋ํธ๋ก ๋๋๊ณ ๊ตฌ์ฑํ๋ ๊ฒ์ด ๋ ์ค์ํฉ๋๋ค. ์ปดํฌ๋ํธ ๋ด๋ถ์์ ํ ํ๋ฆฟ, ๋ก์ง ๋ฐ ์คํ์ผ์ด ๋ณธ์ง์ ์ผ๋ก ๊ฒฐํฉ๋์ด ๋ฐฐ์น๋๋ฉด ์ปดํฌ๋ํธ์ ์์ง๋ ฅ๊ณผ ์ ์ง ๋ณด์์ฑ์ด ํฅ์๋ฉ๋๋ค.
์ฑ๊ธ ํ์ผ ์ปดํฌ๋ํธ์ ๋ํ ์์ด๋์ด๊ฐ ๋ง์์ ๋ค์ง ์๋๋ผ๋ JavaScript์ CSS๋ฅผ ๋ณ๋์ ํ์ผ๋ก ๋ถ๋ฆฌํ์ฌ ํซ ๋ฆฌ๋ก๋ ๋ฐ ์ฌ์ ์ปดํ์ผ ๊ธฐ๋ฅ์ ํ์ฉํ ์ ์์ต๋๋ค.
<template> <div>This section will be pre-compiled and hot reloaded</div> </template> <script src="./my-component.js"></script> <style src="./my-component.css"></style>
-
๋ณต์กํ ํ๋ก์ ํธ์ ๊ฒฝ์ฐ ๋๋ ํ๋ก ํธ์๋๊ฐ JavaScript ๊ธฐ๋ฐ์ธ ๊ฒฝ์ฐ ๋จ์ ์ด ๋ถ๋ช ํด์ง๋๋ค. ์ฑ๊ธ ํ์ผ ์ปดํฌ๋ํธ๊ฐ ์๋ ๊ฒฝ์ฐ์๋ ์๋์ ๊ฐ์ ๋ฌธ์ ์ ์ด ์์ ์ ์์ต๋๋ค.
- ์ ์ญ ์ ์ ๋ชจ๋ ๊ตฌ์ฑ ์์์ ๋ํด ๊ณ ์ ํ ์ด๋ฆ์ ์ง์ ํ๋๋ก ๊ฐ์๋ฉ๋๋ค.
- ๋ฌธ์์ด ํ ํ๋ฆฟ ๊ตฌ๋ฌธ ๊ฐ์กฐ๊ฐ ์ฝํด ์ฌ๋ฌ ์ค๋ก ๋ HTML์ ๋ณด๊ธฐ ์์ข์ ์ฌ๋์๊ฐ ๋ง์ด ํ์ํฉ๋๋ค.
- CSS ์ง์ ์์ HTML ๋ฐ JavaScript๊ฐ ์ปดํฌ๋ํธ๋ก ๋ชจ๋ํ ๋์ด ์์ผ๋ CSS๊ฐ ๋น ์ ธ ์๋ ๊ฒ์ ๋งํฉ๋๋ค.
- ๋น๋ ๋จ๊ณ ์์ Pug (์ด์ ์ Jade) ๋ฐ Babel๊ณผ ๊ฐ์ ์ ์ฒ๋ฆฌ๊ธฐ๊ฐ ์๋ HTML ๋ฐ ES5 JavaScript๋ก ์ ํ๋ฉ๋๋ค.
์ฑ๊ธ ํ์ผ ์ปดํฌ๋ํธ๋ JavaScript ๊ธฐ๋ฐ์์ ๋ฐ์ํ๋ ๋ฌธ์ ์ ์ ํด๊ฒฐํ๊ธฐ ์ํด, ๋ณ๋์
.vue
ํ์ฅ์์ ํ์ผ๋ก ์์ฑํฉ๋๋ค. -
filter
๋ ํ ์คํธ ํ์ํ๋ฅผ ์ํด ์ฌ์ฉ๋ฉ๋๋ค. ์ด ํํฐ๋ค์ ์๋ฐ์คํฌ๋ฆฝํธ ํํ์์ ํ์ดํ(|
) ๊ธฐํธ์ ํจ๊ป ์ถ๊ฐ๋์ด์ผ ํฉ๋๋ค. ํฌ๊ฒ ๋ ๊ฐ์ง ๊ฒฝ์ฐ์์ ์ฌ์ฉ๋ ์ ์์ต๋๋ค.- ์ค๊ดํธ ๋ณด๊ฐ๋ฒ
v-bind
ํํ์
์ฒซ ๊ธ์๋ฅผ ๋๋ฌธ์๋ก ๋ง๋๋ ๋ก์ปฌ ํํฐ๋ฅผ ์ ์ํด๋ด ์๋ค.
filters: { capitalize: function (value) { if (!value) return '' value = value.toString() return value.charAt(0).toUpperCase() + value.slice(1) } }
์ด ํํฐ๋ฅผ ์ค๊ดํธ ๋ณด๊ฐ๋ฒ ๋๋
v-bind
ํํ์ ํจ๊ป ์ฌ์ฉํ ์ ์์ต๋๋ค.<!-- in mustaches --> {{ username | capitalize }} <!-- in v-bind --> <div v-bind:id="username | capitalize"></div>
-
- ์ง์ญ ํํฐ(Local filters): ์ง์ญ ํํฐ๋ ์ปดํฌ๋ํธ์ ์ต์ ์์ ์ ์ํ ์ ์์ต๋๋ค. ์ด ๊ฒฝ์ฐ, ํํฐ๋ ํด๋น ์ปดํฌ๋ํธ์์๋ง ์ฌ์ฉ ๊ฐ๋ฅํฉ๋๋ค.
filters: { capitalize: function (value) { if (!value) return '' value = value.toString() return value.charAt(0).toUpperCase() + value.slice(1) } }
- ์ ์ญ ํํฐ(Global filters): Vue ์ธ์คํด์ค๋ฅผ ๋ง๋ค๊ธฐ ์ ์ ์ ์ญ์ ์ผ๋ก ํํฐ๋ฅผ ์ ์ํ ์ ์์ต๋๋ค. ์ด ๊ฒฝ์ฐ Vue ์ธ์คํด์ค ๋ด์ ๋ชจ๋ ์ปดํฌ๋ํธ์์ ํํฐ๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
Vue.filter('capitalize', function (value) { if (!value) return '' value = value.toString() return value.charAt(0).toUpperCase() + value.slice(1) }) new Vue({ // ... })
-
์ผ๋ฐ์ ์ผ๋ก ์๋์ ๊ฐ์ด, ํํ์์์ ํํฐ ๋ค์ ๋ ๋ค๋ฅธ ํํฐ๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
{{ message | filterA | filterB | filterC ... }}
๊ฐ๊ฐ์ ํํฐ๋ ํ์ดํ(
|
)๋ก ๊ตฌ๋ถ๋๋ฉฐ,message
๋filterA
์ ๊ฒฐ๊ณผ๊ฐfilterB
์ ์ํฅ์ ๋ฐ๊ณ , ๊ทธ ๊ฒฐ๊ณผ๊ฐ ๋ค์filterC
์ ์ํฅ์ ๋ฐ์ต๋๋ค.์๋ฅผ ๋ค์ด, ๋ ์ง ํ์์ ๋ฐ์ดํฐ๋ฅผ ๋ณ๊ฒฝํ ๋ค ๋๋ฌธ์๋ก ๋ณ๊ฒฝํ๊ณ ์ถ๋ค๋ฉด ์๋์ ๊ฐ์ด ์ฌ์ฉํ ์ ์์ต๋๋ค.
{{ birthday | dateFormat | uppercase }}
-
ํํฐ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์๋ฐ์คํฌ๋ฆฝํธ ํจ์์ด๊ธฐ ๋๋ฌธ์, ์๋์ ๊ฐ์ด ๋ ๊ฐ ์ด์์ ์ธ์๋ฅผ ๋ฐ์ ์ ์์ต๋๋ค.
{{ message | filterA('arg1', arg2) }}
์ฌ๊ธฐ์
filterA
๋ ์ธ ๊ฐ์ ์ธ์๋ฅผ ๋ฐ๋ ํจ์๋ก ์ ์๋์์ต๋๋ค.message
์ ๊ฐ์ ์ฒซ๋ฒ์งธ ์ธ์๋ก ์ ๋ฌ๋ ๊ฒ์ด๋ฉฐ, ์์ ๋ฌธ์์ด์ธ'arg1'
์ ๋๋ฒ์งธ ์ธ์๋ก ์ ๋ฌ๋ ๊ฒ์ด๋ฉฐ, ์๋ฐ์คํฌ๋ฆฝํธ ํํ์์ธarg2
๋ ํํ์์ด ์คํ๋ ์ดํ์ ์ธ๋ฒ์งธ ์ธ์๋ก ์ ๋ฌ๋ ๊ฒ์ ๋๋ค.{{ 2 | exponentialStrength(10) }} // prints 2 power 10 = 1024
-
ํ๋ฌ๊ทธ์ธ์ ์ผ๋ฐ์ ์ผ๋ก ์ ์ญ ์์ค ๊ธฐ๋ฅ์ Vue ์ดํ๋ฆฌ์ผ์ด์ ์ ์ถ๊ฐํฉ๋๋ค.
- ์ ์ญ ๋ฉ์๋ ๋๋ ์์ฑ ์ถ๊ฐ(
<vue-custom-element>
) - ํ๋ ์ด์์ ๊ธ๋ก๋ฒ ์์ ์ถ๊ฐ(์ง์์, ํํฐ, ํธ๋์ง์ )
- ์ ์ญ ๋ฏน์ค์ธ์ผ๋ก ์ปดํฌ๋ํธ ์ต์ (vuex)
Vue.prototype
๋ฅผ ์ด์ฉํด Vue์ ์ธ์คํด์ค ๋ฉ์๋๋ฅผ ์ถ๊ฐ- ์์ ๊ธฐ๋ฅ๊ณผ ํจ๊ป ์์ฒด API๋ฅผ ์ ๊ณตํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ(vue-router)
- ์ ์ญ ๋ฉ์๋ ๋๋ ์์ฑ ์ถ๊ฐ(
-
ํ๋ฌ๊ทธ์ธ์์๋
install
๋ฉ์๋๋ฅผ ์ ์ํด์ผ ํฉ๋๋ค. ์ด ๋ฉ์๋๋ ์ฒซ ๋ฒ์งธ ์ธ์๋ก Vue ์์ฑ์์ ์ธ๋ถ์์ ์ค์ ๊ฐ๋ฅํ ์ต์ ์ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌ๋ฐ์ต๋๋ค.MyPlugin.install = function (Vue, options) { // 1. add global method or property Vue.myGlobalMethod = function () { // some logic ... } // 2. add a global asset Vue.directive('my-directive', { bind (el, binding, vnode, oldVnode) { // some logic ... } ... }) // 3. inject some component options Vue.mixin({ created: function () { // some logic ... } ... }) // 4. add an instance method Vue.prototype.$myMethod = function (methodOptions) { // some logic ... } }
-
Vue.use()
์ ์ญ ๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ ํ๋ฌ๊ทธ์ธ์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด ํจ์๋ ์์ฑ์new Vue()
๋ก Vue ์ธ์คํด์ค๋ฅผ ์์ฑํ๊ธฐ ์ ์ ํธ์ถ๋์ด์ผ ํฉ๋๋ค.// calls `MyPlugin.install(Vue, { someOption: true })` Vue.use(MyPlugin) new Vue({ //... options })
-
Mixins๋ Vue ์ปดํฌ๋ํธ์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ๊ธฐ๋ฅ์ ๋ฐฐํฌํ๋ ์ ์ฐํ ๋ฐฉ๋ฒ์ ๋๋ค. ๋ฏน์ค์ธ์ ์กด์ฌํ๋ ๊ธฐ๋ฅ๋ค์ ํธ์ถ๋ ์ปดํฌ๋ํธ์ ๊ธฐ๋ฅ๋ค๊ณผ ํฉ์ณ์ง๋๋ค.
mixin ๊ฐ์ฒด๋ ๋ชจ๋ ๊ตฌ์ฑ ์์ ์ต์ ์ ํฌํจํ ์ ์์ต๋๋ค. ๋ค๋ฅธ ์ปดํฌ๋ํธ์์ ์ฌ์ฌ์ฉ๋ ์ ์๋
created
๋ผ์ดํ์ฌ์ดํด ํ ์ ๊ฐ์ง ๋ฏน์ค์ธ์ ์์ฑํด๋ด ์๋ค.const myMixin = { created(){ console.log("Welcome to Mixins!") } } var app = new Vue({ el: '#root', mixins: [myMixin] })
Note: ์ฌ๋ฌ ๋ฏน์ค์ธ์ ๋ฐฐ์ด์ ํํ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค.
-
Vue ์ดํ๋ฆฌ์ผ์ด์ ์ ๋ชจ๋ ์ปดํฌ๋ํธ์ ๋์ผํ ์ต์ ์ด๋ ๊ธฐ๋ฅ์ ํ์ฅํด ์ฌ์ฉํ ํ์๊ฐ ์์ ์ ์์ต๋๋ค. ์ด ๊ฒฝ์ฐ, ์ ์ญ ๋ฏน์ค์ธ์ ํ์ฉํด Vue์ ๋ชจ๋ ์ปดํฌ๋ํธ์ ์ํฅ์ ์ค ์ ์์ต๋๋ค.
Vue.mixin({ created(){ console.log("Write global mixins") } }) new Vue({ el: '#app' })
์์ ์ ์ญ ๋ฏน์ค์ธ์ ํด๋น Vue ์ธ์คํด์ค์์ ๊ฐ ์ปดํฌ๋ํธ๊ฐ ์์ฑ๋ ๋๋ง๋ค
created
ํ ์์ ๋ก๊ทธ๋ฅผ ๋ฐ์์ํต๋๋ค. ์ฆ ๋ชจ๋ ๋จ์ผ Vue ์ธ์คํด์ค์ ์ํฅ์ ์ฃผ๊ธฐ ๋๋ฌธ์ ์ ๊ฒ ์ด์ฉํ๊ณ ์ ์คํ๊ฒ ์ฌ์ฉํด์ผ ํฉ๋๋ค. -
Vue CLI๋ฅผ ์ฌ์ฉํ๋ค๋ฉด, ๋ฏน์ค์ธ์ ์ผ๋ฐ์ ์ผ๋ก
/src/mixins
๋๋ ํ ๋ฆฌ์์.js
ํ์ผ๋ก ์์ฑํฉ๋๋ค.export
ํค์๋๋ก ์ธ๋ถ์ ๋ด๋ณด๋ธ๋ค๋ ๊ฒ์ ์ ์ธํด์ผ ํ๋ฉฐ ์ฌ์ฉํ Vue ์ปดํฌ๋ํธ์์import
ํค์๋๋ก ๋ถ๋ฌ์ฌ ์ ์์ต๋๋ค. -
๋ฏน์ค์ธ๊ณผ ์ปดํฌ๋ํธ์์ ์ถฉ๋ํ๋ ์ต์ ์ด ์๋ค๋ฉด, ์ต์ ์ ๋ช ๊ฐ์ง ๋ฐฉ๋ฒ์ ํตํด ์ถฉ๋ํ๋ ์ต์ ์ ๋ณํฉํฉ๋๋ค.
data
๋ ์ฌ๊ท์ ์ผ๋ก ๋ณํฉํ๋, ์ถฉ๋๋๋ ์์ฑ์ ์ปดํฌ๋ํธ์ ๋ฐ์ดํฐ๊ฐ ์ฐ์ ์ ์ผ๋ก ๋ณํฉ๋ฉ๋๋ค.
var mixin = { data: function () { return { message: 'Hello, this is a Mixin' } } } new Vue({ mixins: [mixin], data: function () { return { message: 'Hello, this is a Component' } }, created: function () { console.log(this.$data); // => { message: "Hello, this is a Component'" } } })
- ๋ผ์ดํ์ฌ์ดํด ํ ํจ์๋ ๋ฏน์ค์ธ ํจ์๊ฐ ๋จผ์ ์คํ๋๊ณ , ๊ทธ ๋ค์์ ์ปดํฌ๋ํธ์ ํจ์๊ฐ ์คํ๋ฉ๋๋ค.
const myMixin = { created(){ console.log("Called from Mixin") } } new Vue({ el: '#root', mixins:[myMixin], created(){ console.log("Called from Component") } }) //Called from Mixin //Called from Component
methods
,components
,directives
์ญ์ ์ฌ๊ท์ ์ผ๋ก ๋ณํฉํ๋, ์ด๋ฌํ ๊ฐ์ฒด์ ์ถฉ๋ํ๋ ํค๊ฐ ์์ ๊ฒฝ์ฐ ์ปดํฌ๋ํธ์ ์ต์ ์ด ์ฐ์ ์์๋ฅผ ๊ฐ์ต๋๋ค.
var mixin = { methods: { firstName: function () { console.log('John') }, contact: function () { console.log('+65 99898987') } } } var vm = new Vue({ mixins: [mixin], methods: { lastName: function () { console.log('Murray') }, contact: function () { console.log('+91 893839389') } } }) vm.firstName() // "John" vm.lastName() // "Murray" vm.contact() // "+91 893839389"
-
Vue์์๋ ์ฌ์ฉ์ ์ง์ ์ต์ ์ ๋ณํฉํ ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๊ธฐ์กด ๊ฐ์ ๋ฎ์ด๋ ๋ฐฉ๋ฒ์ ์ด์ฉํฉ๋๋ค. ๋ง์ฝ ์ฌ์ฉ์ ์ ์์ ๋ก์ง์ ์ฌ์ฉํด ์ปค์คํ ์ต์ ์ ๋ณํฉํ๋ ค๋ฉด,
Vue.config.optionMergeStrategies
์ ํจ์๋ฅผ ์ถ๊ฐํ ํ์๊ฐ ์์ต๋๋ค.Vue.config.optionMergeStrategies.myOption = function (toVal, fromVal) { // return mergedVal }
๋ ๊ณ ๊ธ ์์ ๋ Vuex์ 1.x ๋ณํฉ ์ ๋ต์์ ํ์ธํ์ค ์ ์์ต๋๋ค.
const merge = Vue.config.optionMergeStrategies.computed Vue.config.optionMergeStrategies.vuex = function (toVal, fromVal) { if (!toVal) return fromVal if (!fromVal) return toVal return { getters: merge(toVal.getters, fromVal.getters), state: merge(toVal.state, fromVal.state), actions: merge(toVal.actions, fromVal.actions) } }
-
์ง์์๋ DOM ์๋ฆฌ๋จผํธ์ ๋ถ์ฐฉํ ์ ์๋ ๋ช ๋ น์ด์ ๋๋ค. ์์์ ๋ณธ ๊ฒ์ฒ๋ผ
v-
๋ก ์์ํ๋ ๋ฌธ๋ฒ์ ์ฌ์ฉํด Vue๊ฐ ์ด ๋ช ๋ น์ด๋ฅผ ์ธ์ํ ์ ์๋๋ก ํด์ผ ํฉ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก ํ์ ์์ค์ DOM์ ์ ์ดํ๊ธฐ ์ํด ์ง์ ์ ๊ทผํด์ผ ํ ํ์๊ฐ ์์ ๋ ์ ์ฉํ๊ฒ ์ฌ์ฉ๋ฉ๋๋ค.ํ์ด์ง๊ฐ ๋ก๋๋ ๋
input
์ ์๋์ผ๋ก ํฌ์ปค์ฑ๋๋ ์ฌ์ฉ์ ์ ์ ์ง์์๋ฅผ ์ ์ญ์ผ๋ก ๋ง๋ค์ด๋ด ์๋ค.// Register a global custom directive called `v-focus` Vue.directive('focus', { // When the bound element is inserted into the DOM... inserted: function (el) { // Focus the element el.focus() } })
์ด์ ์ด ์ง์์๋
v-focus
๋ผ๋ ๋ฌธ๋ฒ๊ณผ ํจ๊ป ์ด๋ค ์ปดํฌ๋ํธ์์๋ ์ฌ์ฉ๋ ์ ์์ต๋๋ค.<input v-focus>
-
์ง์์๋ฅผ ์ง์ญ ๋ฑ๋กํ๊ธฐ ์ํด์๋
directives
์ต์ ์ ์ด์ฉํฉ๋๋ค.directives: { focus: { // directive definition inserted: function (el) { el.focus() } } }
์ด ์ง์์๋ ์ ์ธ๋ ํด๋น ์ปดํฌ๋ํธ์์๋ง ์ฌ์ฉ๋ ์ ์์ต๋๋ค.
<input v-focus>
-
์ง์์ ๊ฐ์ฒด๊ฐ ๋ฑ๋ก๋ ๋ ๋ช ๊ฐ์ ๋ผ์ดํ ์ฌ์ดํด ํ ์ ์ ๊ณตํฉ๋๋ค.
bind
: ์ง์์๊ฐ ์ฒ์ ์๋ฆฌ๋จผํธ์ ๋ถ์ฐฉ๋ ๋ ํ ๋ฒ ํธ์ถ๋ฉ๋๋ค.inserted
: ์ง์์๊ฐ ๋ถ์ฐฉ๋ ์๋ฆฌ๋จผํธ๊ฐ DOM์ ์ฝ์ ๋์์ ๋ ํธ์ถ๋ฉ๋๋ค.update
: ํด๋น ์๋ฆฌ๋จผํธ๊ฐ ์ ๋ฐ์ดํธ ๋ ๋ ํธ์ถ๋ฉ๋๋ค. ํ์ง๋ง ์์ง ํ์ ์๋ฆฌ๋จผํธ๋ ์ ๋ฐ์ดํธ ๋์ง ์์ ์ํ์ ๋๋ค.componentUpdated
: ํ์ ์ปดํฌ๋ํธ๊น์ง ์ ๋ฐ์ดํธ ๋ ์ํ์ผ ๋ ํธ์ถ๋ฉ๋๋ค.unbind
: ์ง์์๊ฐ ์๋ฆฌ๋จผํธ์์๋ถํฐ ์ญ์ ๋ ๋ ํธ์ถ๋ฉ๋๋ค.
Note: ์์ ํ ์์๋ ํน์ ํ ์ ๋ฌ์ธ์(Argument)๋ฅผ ๋ฐ๋๋ค.
-
๋ชจ๋ ํ ์์๋ ์ ๋ฌ์ธ์๋ก
el
,binding
์vnode
๋ฅผ ๊ฐ๊ณ ์์ต๋๋ค. ๊ทธ์ ํจ๊ป, update์ componentUpdatedํ ์์๋ ์ ๊ฐ๊ณผ ์ด์ ๊ฐ์ ๋น๊ตํ๊ธฐ ์ํดoldVnode
๋ฅผ ์ถ๊ฐ์ ์ผ๋ก ๊ฐ๊ณ ์์ต๋๋ค.el
: ํด๋น ์ง์์๊ฐ ๋ถ์ฐฉ๋ ์๋ฆฌ๋จผํธ๋ก, ์ด๋ฅผ ์ด์ฉํด DOM์ ์กฐ์ํ ์ ์์ต๋๋ค.binding
: ์๋์ ์์ฑ์ ๊ฐ์ง ๊ฐ์ฒด์ ๋๋ค.name
: ์ง์์์ ์ด๋ฆ์ผ๋ก,v-
์ ๋์ฌ๊ฐ ์ ๊ฑฐ๋ ์ด๋ฆ์ ๋๋ค.value
: ์ง์์์์ ์ ๋ฌ ๋ฐ์ ๊ฐ์ ๋๋ค. ๋ง์ฝv-my-directive="1 + 1"
๋ผ๋ฉด2
๊ฐ ๋ฉ๋๋ค.oldValue
: ์ด์ ๊ฐ์ผ๋ก,update
์componentUpdated
ํ ์์๋ง ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด๋ฅผ ํตํด ๊ฐ์ด ๋ณ๊ฒฝ๋์๋์ง ์๋์ง๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.expression
: ๋ฌธ์์ด๋ก ๋ฐ์ธ๋ฉ๋ ํํ์์ ๋๋ค. ๋ง์ฝv-my-directive="1 + 1"
๋ผ๋ฉด"1 + 1"
์ด ๋ฉ๋๋ค..arg
: ์ง์์์ ์ ๋ฌ์ธ์์ ๋๋ค. ๋ง์ฝv-my-directive:foo
๋ผ๋ฉด"foo"
๊ฐ ๋ฉ๋๋ค..modifiers
: ์์์ด๊ฐ ํฌํจ๋ ๊ฐ์ฒด์ ๋๋ค. ๋ง์ฝv-my-directive.foo.bar
๋ผ๋ฉด{ foo: true, bar: true }
๊ฐ ๋ฉ๋๋ค.
vnode
: Vue์ ์ปดํ์ผ๋ฌ์ ์ํด ์์ฑ๋ ๊ฐ์ ๋ ธ๋์ ๋๋ค.oldVnode
: ์ด ์ ์ ๊ฐ์ ๋ ธ๋๋ก,update
์componentUpdated
ํ ์์๋ง ์ฌ์ฉํ ์ ์์ต๋๋ค.
-
์ง์์๋ ์ ํจํ ์๋ฐ์คํฌ๋ฆฝํธ ํํ์์ ๋ชจ๋ ์์ฉํ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ ์ง์ฌ์์ ์ฌ๋ฌ ๊ฐ๋ค์ ์ ๋ฌํ๋ ค๋ฉด, ์๋ฐ์คํฌ๋ฆฝํธ ๊ฐ์ฒด ๋ฆฌํฐ๋ด์ ์ด์ฉํด ์ ๋ฌํ ์ ์์ต๋๋ค.
<div v-avatar="{ width: 500, height: 400, url: 'path/logo', text: 'Iron Man' }"></div>
์ด์
v-avatar
์ง์์๋ฅผ ์ ์ญ์ผ๋ก ์ค์ ํด๋ด ์๋ค.Vue.directive('avatar', function (el, binding) { console.log(binding.value.width) // 500 console.log(binding.value.height) // 400 console.log(binding.value.url) // path/logo console.log(binding.value.text) // "Iron Man" })
-
๋๋ฌธ ๊ฒฝ์ฐ์ง๋ง, ๋ค๋ฅธ ํ ๊ณผ๋ ์๊ด์์ด
bind
๋update
ํ ์์ ๊ฐ์ ๋์์ ํ๊ธฐ ์ํ ์ ์์ต๋๋ค. ์ด ๊ฒฝ์ฐ์๋ ํจ์ ์ฝ์ด๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.Vue.directive('theme-switcher', function (el, binding) { el.style.backgroundColor = binding.value })
-
์ผ๋ฐ์ ์ธ ๊ฒฝ์ฐ Vue์ ํ ํ๋ฆฟ์ ์ด์ฉํด HTML์ ์์ฑํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค. ํ์ง๋ง
input
์ด๋slot
์ ๊ฐ์ ์ด์ฉํด ๋์ ์ธ ์ปดํฌ๋ํธ๋ฅผ ์์ฑํ๋ ๊ฒ์ฒ๋ผ, ์ผ๋ถ ํน๋ณํ ๊ฒฝ์ฐ์๋ JavaScript๊ฐ ํ์ํ ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค. ์ด๋render
ํจ์๋ฅผ ์ฌ์ฉํ๋ฉฐ,render
ํจ์๋ JavaScript๋ก ์์ฑํ๊ธฐ ๋๋ฌธ์ ํ๋ก๊ทธ๋๋ฐ ํ๊ฒฝ์ ์จ์ ํ ์ด์ฉํ ์ ์๋ค๋ ์ฅ์ ์ด ์์ต๋๋ค. -
render
ํจ์๋createElement
๋ผ๋ ํจ์๋ฅผ ์ฒซ ๋ฒ์งธ ์ธ์๋ก ๋ฐ์ ๊ฐ์ ๋ ธ๋๋ฅผ ์์ฑํ๋ ํจ์์ ๋๋ค. ๋ด๋ถ์ ์ผ๋ก Vue์ ํ ํ๋ฆฟ์ ๋น๋ ํ์์์render
ํจ์๋ฅผ ์ด์ฉํด ์ปดํ์ผํ๊ณ ์์ต๋๋ค. ๊ทธ๋ฌ๋ฏ๋ก ํ ํ๋ฆฟ์render
ํจ์๋ฅผ ๋ฌธ๋ฒ์ ์ผ๋ก ๋ณด๊ธฐ ์ฝ๊ฒ ๋ง๋ค์ด ๋์ ๊ฒ์ ๊ฐ๊น์ต๋๋ค.<div>
ํ๊ทธ๋ฅผ ๊ฐ๊ฐ ํ ํ๋ฆฟ๊ณผrender
ํจ์๋ฅผ ์ด์ฉํด ์์ฑํด๋ด ์๋ค. ํ ํ๋ฆฟ์ ์๋์ ๊ฐ์ด ์์ฑํ ์ ์์ต๋๋ค.<template> <div :class="{'is-rounded': isRounded}"> <p>Welcome to Vue render functions</p> </div> </template>
์์ ํ ํ๋ฆฟ์
render
ํจ์๋ก ์์ฑํ๋ฉด ์๋์ ๊ฐ์ต๋๋ค.render: function (createElement) { return createElement('div', { 'class': { 'is-rounded': this.isRounded } }, [ createElement('p', 'Welcome to Vue render functions') ]); },
-
createElement
ํจ์๋ ๋ช ๊ฐ์ง์ ์ฝ์๋ ์ ๋ฌ์ธ์๋ฅผ ๋ฐ๋๋ฐ, ์ด๋ฅผ ์ด์ฉํด ํ ํ๋ฆฟ์์ ์ฌ์ฉ๋๋ ๊ธฐ๋ฅ์ JavaScript ์ฝ๋๋ก ์์ฑํ ์ ์์ต๋๋ค.// @returns {VNode} createElement( // An HTML tag name, component options, or async function resolving to one of these. // Type is {String | Object | Function} // Required. 'div', // A data object corresponding to the attributes you would use in a template. //Type is {Object} // Optional. { // Normal HTML attributes attrs: { id: 'someId' }, // Component props props: { myProp: 'somePropValue' }, // DOM properties domProps: { innerHTML: 'This is some text' }, // Event handlers are nested under `on` on: { click: this.clickHandler }, // Similar to `v-bind:style`, accepting either a string, object, or array of objects. style: { color: 'red', fontSize: '14px' }, //Similar to `v-bind:class`, accepting either a string, object, or array of strings and objects. class: { classsName1: true, classsName2: false }, .... }, // Children VNodes, built using `createElement()`, or using strings to get 'text VNodes'. // Type is {String | Array} // Optional. [ 'Learn about createElement arguments.', createElement('h1', 'Headline as a child virtual node'), createElement(MyComponent, { props: { someProp: 'This is a prop value' } }) ] )
์์ธํ ๋ด์ฉ์ ๊ณต์ ๋ฌธ์์์ ํ์ธํ ์ ์์ต๋๋ค.
-
์ปดํฌ๋ํธ ํธ๋ฆฌ์ ๋ชจ๋ ๊ฐ์ ๋ ธ๋(VNodes)๋ ๊ณ ์ ํด์ผ ํฉ๋๋ค. ์ฆ, ์ง์ ๊ฐ์ ๋ ธ๋๋ฅผ ์ฌ๋ฌ ๋ฒ ์ฌ์ฉํ ์๋ ์์ต๋๋ค. ๋ง์ฝ ๊ฐ์ ์๋ฆฌ๋จผํธ๋ ์ปดํฌ๋ํธ๋ฅผ ์ฌ๋ฌ ๋ฒ ๋ฐ๋ณตํด์ ์ฌ์ฉํด์ผ ํ๋ค๋ฉด, ํฉํ ๋ฆฌ ํจํด์ ์ด์ฉํด ์์ฑํด์ผ ํฉ๋๋ค.
์๋์
render
ํจ์๋h1
์๋ฆฌ๋จผํธ๋ฅผ ์ธ ๋ฒ ๋ฐ๋ณตํ๋ ค ํ๊ธฐ ๋๋ฌธ์ ์ ํจํ์ง ์์ต๋๋ค.render: function (createElement) { var myHeadingVNode = createElement('h1', 'This is a Virtual Node') return createElement('div', [ myHeadingVNode, myHeadingVNode, myHeadingVNode ]) }
ํฉํ ๋ฆฌ ํจํด์ ์ด์ฉํ๋ฉด ๋ฉ๋๋ค.
render: function (createElement) { return createElement('div', Array.apply(null, { length: 3 }).map(function () { return createElement('h1', 'This is a Virtual Node') }) ) }
-
Vue์์ HTML์ ์์ฑํ๋๋ฐ ์ฌ์ฉ๋๋ ํ ํ๋ฆฟ๊ณผ render ํจ์๋ฅผ ๋น๊ตํด๋ด ์๋ค.
ํ ํ๋ฆฟ(Templates) ๋ ๋ ํจ์(Render function) v-if
์v-for
๋ฅผ ์ด์ฉํด ์กฐ๊ฑด๋ฌธ/๋ฐ๋ณต๋ฌธ ์คํJavaScript์ if else
๋ฌธ๊ณผmap
๋ฉ์๋๋ก ์กฐ๊ฑด๋ฌธ/๋ฐ๋ณต๋ฌธ ์คํv-model
๋ก ์๋ฐฉํฅ ๋ฐ์ธ๋ฉ๋ฐ์ธ๋ฉ๊ณผ ์ด๋ฒคํธ๋ฅผ ์ง์ ์ค์ Capture ์ด๋ฒคํธ ์์์ด๋ .passive
,.capture
,.once,
.capture.once
,.once.capture
&, !, ~, ~! ์ด๋ฒคํธ ์์์ด์ ํค ์์์ด: .stop
,.prevent
,.self
, keys(.enter
,.13
) and Modifiers Keys(.ctrl
,.alt
,.shift
,.meta
)JavaScript๋ก ํด๊ฒฐ, event.stopPropagation()
,event.preventDefault()
,if (event.target !== event.currentTarget) return
,if (event.keyCode !== 13) return
,if (!event.ctrlKey) return
์ฌ๋กฏ ์์ฑ ํ์ฉ ๋ ๋ ํจ์์ this.$slots
์this.$scopedSlots
ํ์ฉ -
ํจ์ํ ์ปดํฌ๋ํธ๋
context
๋ฅผ ํตํด ์ ๋ฌ๋ฐ์ ์ ๋ณด๋ก๋ง ์์ฑ๋๋ ๊ฐ๋จํ ์ปดํฌ๋ํธ์ ๋๋ค.- ์ํ ์์(Stateless): ์ฆ
data
๊ฐ ์์ต๋๋ค - ์ธ์คํด์ค ์์(Instanceless): ์ฆ
this
๊ฐ ์์ต๋๋ค
์ด ๊ฒฝ์ฐ,
functional: true
์์ฑ์ ์ด์ฉํด ์ปดํฌ๋ํธ๋ฅผ ํจ์ํ์ผ๋ก ์์ฑํ ์ ์์ต๋๋ค.Vue.component('my-component', { functional: true, // Props are optional props: { // ... }, // To compensate for the lack of an instance, // we are now provided a 2nd context argument. render: function (createElement, context) { // ... } })
- ์ํ ์์(Stateless): ์ฆ
-
- ๋ ํ๋ ์์ํฌ ๋ชจ๋ ๊ฐ์ DOM ๋ชจ๋ธ์ ์ฌ์ฉํฉ๋๋ค.
- ๋ฐ์์ ์ด๊ณ ์กฐํฉ ๊ฐ๋ฅํ ์ปดํฌ๋ํธ๋ฅผ ์ ๊ณตํฉ๋๋ค.
- ์ฝ์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ์๋ง ์ง์คํ๊ณ ์๊ณ , ๋ผ์ฐํ ๋ฐ ์ํ ๊ด๋ฆฌ์ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๋ถ๊ฐ์ ์ผ๋ก ์์ต๋๋ค.
-
ํน์ง Vue React ํ์ JavaScript MVC ํ๋ ์์ํฌ JavaScript ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ๋ซํผ ์น์ ์ฐ์ ์ ์ผ๋ก ์น๊ณผ ๋ค์ดํฐ๋ธ ๋ชจ๋ ๋ณต์ก๋ ์๋์ ์ผ๋ก ๊ฐ๋จ ์๋์ ์ผ๋ก ๋ณต์ก ๋น๋ ์ดํ๋ฆฌ์ผ์ด์ Vue-cli CRA ( Create-React-App
) -
- ๊ฐ๋ณ๊ณ ๋น ๋ฆ ๋๋ค.
- ํ ํ๋ฆฟ์ด ๊ฐ๋ฐ ๊ณผ์ ์ ์ฝ๊ฒ ๋ง๋ค์ด์ค๋๋ค.
- JSX์ ๋นํด ๊ฐ๋ฒผ์ด JavaScript ๋ฌธ๋ฒ์ ์ฌ์ฉํฉ๋๋ค.
-
- ํฐ ๊ท๋ชจ์ ์ดํ๋ฆฌ์ผ์ด์ ์ ์ ์ฐํ๊ฒ ๋ง๋ค ์ ์์ต๋๋ค.
- ํ ์คํธ๊ฐ ์ฝ์ต๋๋ค.
- ๋ชจ๋ฐ์ผ ์ฑ ์ ์์๋ ์ ํฉํฉ๋๋ค.
- ์ํ๊ณ๊ฐ ํฌ๊ณ ํ๋ถํฉ๋๋ค.
-
Vue์ ๊ฐ๋ฐ ์ด๊ธฐ ๋จ๊ณ์์ AngularJS๋ฅผ ์ฐธ๊ณ ํ๊ธฐ ๋๋ฌธ์, Vue์ AngularJS์ ๋ฌธ๋ฒ์ ์๋นํ ๋น์ทํฉ๋๋ค. ํ์ง๋ง ์ฐจ์ด์ ์ญ์ ์กด์ฌํฉ๋๋ค.
ํน์ง Vue AngularJS ๋ณต์ก๋ ๋ฐฐ์ฐ๊ธฐ ์ฌ์ด API์ ๋์์ธ ํ๋ ์์ํฌ๊ฐ ๊ฝค ํฌ๊ณ ํ์ ์คํฌ๋ฆฝํธ ๋ฑ์ ์ง์ ํ์ ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ ์๋ฐฉํฅ ๋ฐ์ธ๋ฉ ๋จ๋ฐฉํฅ ๋ฐ์ธ๋ฉ ์ด๊ธฐ ๋ฆด๋ฆฌ์ฆ 2014 2016 ๋ชจ๋ธ ๊ฐ์ DOM ๊ธฐ๋ฐ MVC ์์ฑ๋ ์ธ์ด JavaScript TypeScript -
<component>
ํ๊ทธ์์v-bind:is
๋ก ๋ฐ์ธ๋ฉ๋ ์ปดํฌ๋ํธ๋ฅผ ๋์ ์ผ๋ก ์ ํํ ์ ์์ต๋๋ค.new Vue({ el: '#app', data: { currentPage: 'home' }, components: { home: { template: "<p>Home</p>" }, about: { template: "<p>About</p>" }, contact: { template: "<p>Contact</p>" } } })
์ด์ ํ ํ๋ฆฟ์์
<component>
ํ๊ทธ์ ๋ฐ์ธ๋ฉ ๋ ์ปดํฌ๋ํธ๋ฅผ ์ค์ ํ ์ ์์ต๋๋ค.<div id="app"> <component v-bind:is="currentPage"> <!-- component changes when currentPage changes! --> <!-- output: Home --> </component> </div>
-
<keep-alive>
๋ ์ปดํฌ๋ํธ์ ์ํ๋ฅผ ๋ณด์กดํด์ ์ฌ ๋ ๋๋ง์ ๋ง์์ฃผ๋ ์ถ์ ์ปดํฌ๋ํธ์ ๋๋ค. ๋ง์ฝ ๋์ ์ธ ์ปดํฌ๋ํธ๋ฅผ<keep-alive>
ํ๊ทธ๋ก ๊ฐ์ผ๋ค๋ฉด, ์ปดํฌ๋ํธ ์ธ์คํด์ค๋ฅผ ์์ ์ง ์๊ณ ๋ฉ๋ชจ๋ฆฌ์ ์ ์งํด ๋ณด์กดํฉ๋๋ค.<!-- Inactive components will be cached! --> <keep-alive> <component v-bind:is="currentTabComponent"></component> </keep-alive>
๋ง์ฝ ์กฐ๊ฑด๋ฌธ์ด ์๋ค๋ฉด, ํด๋น ์กฐ๊ฑด์ ํ์ ์ปดํฌ๋ํธ๋ง ๋ ๋๋ง๋ฉ๋๋ค.
<!-- multiple conditional children --> <keep-alive> <comp-a v-if="a > 1"></comp-a> <comp-b v-else></comp-b> </keep-alive>
Note:
<keep-alive>
๋ DOM์ ๋ ๋๋ง ๋์ง ์์ต๋๋ค. -
๋๊ท๋ชจ ์์ฉ ํ๋ก๊ทธ๋จ์์๋ ์์ฉ ํ๋ก๊ทธ๋จ์ ๋ ์์ ๋ฉ์ด๋ฆฌ๋ก ๋๋๊ณ ์ค์ ๋ก ํ์ํ ๋๋ง ์๋ฒ์์ ์ปดํฌ๋ํธ๋ฅผ ๋ก๋ํด์ผ ํ ์๋ ์์ต๋๋ค. Vue๋ฅผ ์ฌ์ฉํ๋ฉด ์ปดํฌ๋ํธ ์ ์๋ฅผ ๋น๋๊ธฐ์์ผ๋ก ํด๊ฒฐํ๋ ํฉํ ๋ฆฌ ํจ์๋ก ์ปดํฌ๋ํธ๋ฅผ ์ ์ ํ ์ ์์ต๋๋ค.
Vue.component('async-webpack-example', function (resolve, reject) { // Webpack automatically split your built code into bundles which are loaded over Ajax requests. require(['./my-async-component'], resolve) })
Vue๋ Vue๋ ์ปดํฌ๋ํธ๊ฐ ๋ ๋๋ง๋์ด์ผ ํ ๋๋ง ํฉํ ๋ฆฌ ํจ์๋ฅผ ์คํ์ํค๊ณ , ์ดํ์ ๋์ค์ ์์ ๋ฆฌ๋ ๋๋ง์ ์ํด ๊ฒฐ๊ณผ๋ฅผ ์บ์ํฉ๋๋ค.
-
๋น๋๊ธฐ ์ปดํฌ๋ํธ ํฉํ ๋ฆฌ๋ ๋ค์ ํํ์ ๊ฐ์ฒด๋ฅผ ๋ฐํํ ์ ์์ต๋๋ค.
const AsyncComponent = () => ({ // The component to load (should be a Promise) component: import('./MyComponent.vue'), // A component to use while the async component is loading loading: LoadingComponent, // A component to use if the load fails error: ErrorComponent, // Delay before showing the loading component. Default: 200ms. delay: 200, // The error component will be displayed if a timeout is // provided and exceeded. Default: Infinity. timeout: 3000 })
-
ํ์ ์ปดํฌ๋ํธ์
inline-template
์์ฑ์ด ์กด์ฌํ ๋, ์ปดํฌ๋ํธ๋ ๋ด๋ถ ์ปจํ ์ธ ๋ฅผ ํ ํ๋ฆฟ์ผ๋ก ์ฌ์ฉํฉ๋๋ค. ๋ฐ๋ผ์ ๋ณด๋ค ์ ์ฐํ ํ ํ๋ฆฟ ์์ฑ์ด ๊ฐ๋ฅํฉ๋๋ค.<my-component inline-template> <div> <h1>Inline templates</p> <p>Treated as component component owne content</p> </div> </my-component>
Note:
inline-template
์ ํ ํ๋ฆฟ์ ๋ฒ์๋ฅผ ์ถ๋ก ํ๊ธฐ ์ด๋ ต๊ฒ ๋ง๋ญ๋๋ค. ๊ฐ์ฅ ์ข์ ๋ฐฉ๋ฒ์template
์ต์ ์ ์ฌ์ฉํ๊ฑฐ๋.vue
ํ์ผ์template
์๋ฆฌ๋จผํธ๋ฅผ ์ฌ์ฉํ์ฌ ์ปดํฌ๋ํธ ๋ด๋ถ์ ํ ํ๋ฆฟ์ ์ ์ํ๋ ๊ฒ์ ๋๋ค. -
ํ ํ๋ฆฟ๋ฅผ ์ ์ํ๋ ๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ์
text/x-template
์ ํ์ ์คํฌ๋ฆฝํธ ์๋ฆฌ๋จผํธ ๋ด๋ถ์ ID๋ก ํ ํ๋ฆฟ์ ์ฐธ์กฐํ๋ ๊ฒ์ ๋๋ค.<script type="text/x-template" id="script-template"> <p>Welcome to X-Template feature</p> </script>
Vue.component('x-template-example', { template: '#script-template' })
-
์ปดํฌ๋ํธ๋ ์์ ์ ํ ํ๋ฆฟ์์ ์๊ธฐ ์์ ์ ์ฌ๊ท์ ์ผ๋ก ํธ์ถํ ์ ์์ต๋๋ค.
Vue.component('recursive-component', { template: `<!--Invoking myself!--> <recursive-component></recursive-component>` });
์ฌ๊ท ์ปดํฌ๋ํธ๋ ๋ธ๋ก๊ทธ์ ๋ง๊ธ์ด๋ ๋ฉ๋ด์ฒ๋ผ ์์ ์ปดํฌ๋ํธ์ ํ์ ์ปดํฌ๋ํธ๊ฐ ๋๋ฑํ ๊ธฐ๋ฅ์ ํ ๋ ์ ์ฉํฉ๋๋ค.
Note: ์์ ๊ฐ์ ์ปดํฌ๋ํธ๋ ์ต๋ ์คํ ํฌ๊ธฐ ์ด๊ณผ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฏ๋ก ์ฌ๊ท ํธ์ถ์ด ์กฐ๊ฑด๋ถ์ธ์ง ํ์ธํด์ผ ํฉ๋๋ค.
-
๋ณต์กํ ์ดํ๋ฆฌ์ผ์ด์ ์์ Vue ์ปดํฌ๋ํธ๊ฐ ์๋ก๊ฐ ์๋ก๋ฅผ ํธ์ถํ๊ณ ์๋ ์ํฉ์ด ๋ฐ์ํ ์ ์์ต๋๋ค. ์ปดํฌ๋ํธ A์ ์ปดํฌ๋ํธ B๊ฐ ์๋ก ์ํ ์ฐธ์กฐ๋ฅผ ํ๊ณ ์๋ ์ํฉ์ ์ดํด๋ด ์๋ค.
//ComponentA <div> <component-b > </div>
//ComponentB <div> <component-b > </div>
์ด๋ฐ ๊ฒฝ์ฐ๋
beforeCreate
๋ผ์ดํ ์ฌ์ดํด ํ ์์ ๊น์ง ๊ธฐ๋ค๋ ธ๋ค๊ฐ ํด๋น ์ปดํฌ๋ํธ๋ฅผ ๋ฑ๋กํ๊ฑฐ๋, ์นํฉ์ ๋น๋๊ธฐimport
๋ฅผ ํ์ฉํฉ๋๋ค.Solution1:
beforeCreate: function () { this.$options.components.componentB = require('./component-b.vue').default }
Solution2:
components: { componentB: () => import('./component-b.vue') }
-
Google ํฌ๋กฌ ์ฑ๊ณผ ๊ฐ์ ์ผ๋ถ ํ๊ฒฝ์์๋ CSP(์ปจํ ์ธ ๋ณด์ ์ ์ฑ )๋ฅผ ์ ์ฉํ์ฌ ํํ์์ ํ๊ฐํ๋ ๋ฐ
new Function()
์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ ์ฒด ๋น๋๋ ์ด ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ฌ ํ ํ๋ฆฟ์ ์ปดํ์ผํ๋ฏ๋ก ์ด๋ฌํ ํ๊ฒฝ์์๋ ์ฌ์ฉํ ์ ์์ต๋๋ค.๋ฐ๋ฉด ๋ฐํ์ ์ ์ฉ ๋น๋๋ CSP์ ์๋ฒฝํ๊ฒ ํธํ๋ฉ๋๋ค. Webpack + vue-loader ๋๋ Browserify + vueify๋ก ๋ฐํ์ ์ ์ฉ ๋น๋๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ํ ํ๋ฆฟ์ CSP ํ๊ฒฝ์์ ์๋ฒฝํ๊ฒ ์๋ํ๋
render
ํจ์๋ก ๋ฏธ๋ฆฌ ์ปดํ์ผ๋ฉ๋๋ค. -
1. ์ ์ฒด ๋น๋(Full): ์ปดํ์ผ๋ฌ์ ๋ฐํ์ ๋น๋๋ฅผ ๋์์ ํฌํจํฉ๋๋ค. ํ ํ๋ฆฟ์ ์์ฑํ ๊ฒฝ์ฐ ํ์ํฉ๋๋ค.
2. ๋ฐํ์ ๋น๋(Runtime): Vue ์ธ์คํด์ค ์์ฑ๊ณผ
render
ํจ์, ๊ฐ์ ๋์ ํฌํจํ๊ณ ์์ง๋ง ์ปดํ์ผ๋ฌ ๋น๋๋ฅผ ํฌํจํ๊ณ ์์ง ์์ต๋๋ค. -
ํ์ UMD CommonJS ES Module (for bundlers) ES Module (for browsers) ์ ์ฒด ๋น๋ vue.js vue.common.js vue.esm.js vue.esm.browser.js ๋ฐํ์ ๋น๋ vue.runtime.js vue.runtime.common.js vue.runtime.esm.js NA ์ ์ฒด ๋น๋ (๋ฐฐํฌ ๋ชจ๋) vue.min.js NA NA vue.esm.browser.min.js ๋ฐํ์ ๋น๋ (๋ฐฐํฌ ๋ชจ๋) vue.runtime.min.js NA NA NA -
alias
๋ฅผ ์ด์ฉํด Vue๋ฅผ ์ค์ ํ ์ ์์ต๋๋ค.module.exports = { // ... resolve: { alias: { 'vue$': 'vue/dist/vue.esm.js' // 'vue/dist/vue.common.js' for webpack 1 } } }
-
Vue๋ ์ปดํ์ผ๋ฌ๋ฅผ ์ด์ฉํด ํ ํ๋ฆฟ์
render
ํจ์๋ก ๋ณํํฉ๋๋ค.// this requires the compiler new Vue({ template: '<div>{{ message }}</div>' }) // this does not new Vue({ render (h) { return h('div', this.message) } })
-
DevTool์ Vue ์ดํ๋ฆฌ์ผ์ด์ ์ ์ฌ์ฉ์ ์นํ์ ์ธ ์ธํฐํ์ด์ค๋ก ๋๋ฒ๊ทธ ํ ์ ์๊ฒ ๋์์ฃผ๋ ๋ธ๋ผ์ฐ์ ํ์ฅ ํ๋ก๊ทธ๋จ์ ๋๋ค.
Note: Vue ํ์ด์ง๊ฐ ๋ฐฐํฌ ๋ชจ๋์ผ ๊ฒฝ์ฐ์๋ DevTool๋ก ๋๋ฒ๊ทธํ ์ ์์ต๋๋ค.
-
ECMAScript5๋ฅผ ์ง์ํ๋ ๋ธ๋ผ์ฐ์ ์์ ๋์ ๊ฐ๋ฅํฉ๋๋ค. IE8 ์ดํ์ ๋ธ๋ผ์ฐ์ ์์๋ ์ง์ํ์ง ์์ต๋๋ค.
-
Vue๋ jsdelivr, unpkg, cdnjs์์ ์ ๊ณตํ๋ CDN์ ์ด์ฉํด์๋ ์ฌ์ฉ์ด ๊ฐ๋ฅํฉ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก ์ด๊ธฐ ๊ธฐํ, ํ์ต์ฉ์ผ๋ก ์ ํฉํฉ๋๋ค.
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.7/dist/vue.js"></script>
<script type="module"> import Vue from 'https://cdn.jsdelivr.net/npm/vue@2.6.7/dist/vue.esm.browser.js' </script>
Note: ๋ฒ์ ์ ๋ณด๋ฅผ ์ง์ฐ๋ฉด ํญ์ ์ต์ ๋ฒ์ ์ ๊ฐ์ ธ์ต๋๋ค.
-
๋งค์ฐ ๋๋ฌธ ๊ฒฝ์ฐ์ง๋ง, ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋์ง ์์์์๋ ์ฌ ๋ ๋๋ง์ ์ํด ๊ฐ์ ๋ก ์ ๋ฐ์ดํธ๋ฅผ ๋ฐ์์์ผ์ผ ํ ์๋ ์์ต๋๋ค. ์ด ๊ฒฝ์ฐ
vm.$forceUpdate()
API ๋ฉ์๋๋ฅผ ์ด์ฉํ ์ ์์ต๋๋ค.Note: ๋ชจ๋ ํ์ ์ปดํฌ๋ํธ์๋ ์ํฅ์ด ๋ฏธ์น์ง ์์ผ๋ฉฐ, ์ฌ๋กฏ ๊ทธ ์์ฒด๊ฐ ์ฝ์ ๋ ์ฌ๋กฏ ์์ฒด ๋ฐ ํ์ ์ปดํฌ๋ํธ์๋ง ์ํฅ์ ๋ฏธ์นฉ๋๋ค.
-
๋ง์ ์์ ์ ์ ์ปจํ ์ธ ๋ฅผ ๋ ๋๋ง ํ ๋, ์ฑ๋ฅ ํฅ์์ ์ํด ์๋ฆฌ๋จผํธ ๋ฐ ์ปดํฌ๋ํธ๋ฅผ ํ๋ฒ๋ง ๋ ๋๋งํ๋ ์ฉ๋๋ก ์ฌ์ฉํฉ๋๋ค.
Vue.component('legal-terms', { template: ` <div v-once> <h1>Legal Terms</h1> ... a lot of static content goes here... </div> ` })
Note: ์ ์ ์ปจํ ์ธ ๊ฐ ๋ง์์ ๋๋ ค์ง๋ ์ผ์ด ๋ฐ์ํ์ง ์๋ ํ, ๊ณผ๋คํ๊ฒ ์ฌ์ฉํ์ง ์๋ ๊ฒ์ด ์ข์ต๋๋ค.
-
๋ฃจํธ Vue ์ธ์คํด์ค(
new Vue()
)๋$root
์์ฑ์ ์ด์ฉํด ์ ๊ทผํ ์ ์์ต๋๋ค.// The root Vue instance new Vue({ data: { age: 26 }, computed: { fullName: function () { /* ... */ } }, methods: { interest: function () { /* ... */ } } })
๋ฃจํธ ์ธ์คํด์ค์ ๋ฐ์ดํฐ์ ๋ฉ์๋๋ค์ ํ์ ์ปดํฌ๋ํธ์์ ์๋์ ๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก ์ ๊ทผํ ์ ์์ต๋๋ค.
// Get root data this.$root.age // Set root data this.$root.age = 29 // Access root computed properties this.$root.fullName // Call root methods this.$root.interest()
์ํ ๊ด๋ฆฌ๋ฅผ ์ํ ์ฉ๋๋ผ๋ฉด Vuex๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ซ์ต๋๋ค.
-
์๋๋ Vue๋ฅผ ์ฌ์ฉํ๊ณ ์๋ ์ ๋ช ๊ธฐ์ ๋ค์ ๋๋ค.
- Netflix
- Adobe
- Xiaomi
- Alibaba
- WizzAir
- EuroNews
- Laracasts
- GitLab
- Laracasts
-
๊ธฐ๋ณธ
render
ํจ์๊ฐ ๋ ๋๋ง ๋์ค ์๋ฌ๊ฐ ๋ฐ์ํ๋ฉด, ๋์ฒด๋๋ ๋ ๋๋ง ๊ฒฐ๊ณผ๋ฅผ ์ ๊ณตํฉ๋๋ค.renderError
์ ๋ ๋ฒ์งธ ์ ๋ฌ์ธ์๋ก ์๋ฌ๊ฐ ์ ๋ฌ๋ฉ๋๋ค.new Vue({ render (h) { throw new Error('An error') }, renderError (h, err) { return h('div', { style: { color: 'red' }}, err.stack) } }).$mount('#app')
-
์์ ์ปดํฌ๋ํธ์์๋ ํ์ ์ปดํฌ๋ํธ๋ค์
$children
๋ฐฐ์ด๋ก ์ฐธ์กฐํ๋ฉฐ, ํ์ ์ปดํฌ๋ํธ์์ ์์ ์ปดํฌ๋ํธ๋ฅผ$parent
์์ฑ์ผ๋ก ์ฐธ์กฐํฉ๋๋ค. -
Vuex๋ Vue.js ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ ์ํ ๊ด๋ฆฌ ํจํด + ๋ผ์ด๋ธ๋ฌ๋ฆฌ(Flux์์ ์๊ฐ์ ๋ฐ์ ์ ํ๋ฆฌ์ผ์ด์ ์ํคํ ์ฒ)์ ๋๋ค. ์์ธก ๊ฐ๋ฅํ ๋ฐฉ์์ผ๋ก๋ง ์ํ๊ฐ ๋ณ๊ฒฝ๋ ์ ์๋๋ก ๋ณด์ฅํ๋ ๊ท์น์ ๊ฐ์ง๊ณ ์๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ชจ๋ ์ปดํฌ๋ํธ๋ฅผ ์ํ ์ค์ ์ง์ค์ ์ ์ฅ์์ ๋๋ค.
-
์ํ ๊ด๋ฆฌ์ ์ฃผ์ ๊ตฌ์ฑ์์๋ ์ํ ๋ฐ ๋ทฐ, ์ก์ ์ ๋๋ค. ์ด๋ฌํ ๊ตฌ์ฑ์์์ ๋ฐ๋ฅธ ํจํด์ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ํ ๊ด๋ฆฌ ํจํด์ด๋ผ๊ณ ํฉ๋๋ค. ์๋์ ์์ธํ ๊ตฌ์ฑ ์์๊ฐ ์์ธํ ์ค๋ช ๋์ด ์์ต๋๋ค.
- ์ํ๋ ์ฑ์ ๊ตฌ๋์ํค๋ ์์ฒ์ ๋๋ค.
- ๋ทฐ๋ ๋จ์ง ์ํ์ ์ ์ธ์ ๋งคํ์ ๋๋ค.
- ์ก์ ์ ๋ทฐ์์ ์ฌ์ฉ์ ์ ๋ ฅ์ ๋ฐ์ํ์ฌ ์ํ๊ฐ ๋ณํ ์ ์๋๋ก ํ๋ ๋ฐฉ๋ฒ์ ๋๋ค. ์์ 3๊ฐ์ง ๊ตฌ์ฑ์์์ ํจ๊ป ์ํ ๊ด๋ฆฌ ํจํด์ ๋ฐ๋ฅด๋ ์นด์ดํฐ ์๋ฅผ ๋ค์ด๋ณด๊ฒ ์ต๋๋ค.
new Vue({ // state data () { return { count: 0 } }, // view template: ` <div>{{ count }}</div> `, // actions methods: { increment () { this.count++ } } })
-
Vue.js๋ props ์์ฑ์ ํตํด ๋จ๋ฐฉํฅ ๋ฐ์ดํฐ ํ๋ฆ ๋ชจ๋ธ์ ํํํฉ๋๋ค. vuex์์ ์ด์ ๋์ผํ ๊ฐ๋ ์ ์๋์ ๊ฐ์ด ๋ํ๋ผ ์ ์์ต๋๋ค.
-
Vue loader๋ Vue ์ปดํฌ๋ํธ๋ฅผ ์ฑ๊ธ ํ์ผ ์ปดํฌ๋ํธ(SFC, SFCs)๋ผ๊ณ ํ๋ ํ์์ผ๋ก ์์ฑํ ์ ์๋ ์นํฉ์ฉ ๋ก๋์ ๋๋ค. ์๋ฅผ ๋ค์ด
HelloWorld
๋ผ๋ SFC๋ฅผ ์์ฑํ๋ฉด ์๋์ ๊ฐ์ต๋๋ค.<template> <div class="greeting">{{ message }}</div> </template> <script> export default { data () { return { message: 'Hello world for vueloader!' } } } </script> <style> .greeting { color: blue; } </style>
-
Vue-Loader
์ ์ค์ ์ ์นํฉ ์ค์ ์ Vue Loader์ ํ๋ฌ๊ทธ์ธ์ ์ถ๊ฐํ๊ธฐ ๋๋ฌธ์ ๋ค๋ฅธ ๋ก๋์๋ ์ฝ๊ฐ ๋ค๋ฆ ๋๋ค. Vue ๋ก๋ ํ๋ฌ๊ทธ์ธ์ ์ ์๋ ๋ค๋ฅธ ๊ท์น(js ๋ฐ css ๊ท์น)์ ๋ณต์ ํ์ฌ.vue
ํ์ผ์์ ํด๋น ์ธ์ด ๋ธ๋ก(script
๋ฐstyle
)์ ์ ์ฉํ๊ธฐ ์ํด ํ์ํฉ๋๋ค. Vue ๋ก๋์ ๋ํ ์นํฉ ๊ตฌ์ฑ์ ๊ฐ๋จํ ์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.// webpack.config.js const VueLoaderPlugin = require('vue-loader/lib/plugin') module.exports = { mode: 'development', module: { rules: [ { test: /\.vue$/, loader: 'vue-loader' }, // this will apply to both plain `.js` files and `<script>` blocks in `.vue` files { test: /\.js$/, loader: 'babel-loader' }, // this will apply to both plain `.css` files and `<style>` blocks in `.vue` files { test: /\.css$/, use: [ 'vue-style-loader', 'css-loader' ] } ] }, plugins: [ // make sure to include the plugin for cloning and mapping them to respective language blocks new VueLoaderPlugin() ] }
-
- ์ ๋ ๊ฒฝ๋ก(Absolute path): ๋ง์ฝ URL ๊ฒฝ๋ก๊ฐ
/images/loader.png
์ ๊ฐ์ ์ ๋ ๊ฒฝ๋ก๋ผ๋ฉด ๊ทธ๋๋ก ๋ณด์กด๋ฉ๋๋ค. - ์๋ ๊ฒฝ๋ก(Relative path): ๋ง์ฝ URL ๊ฒฝ๋ก๊ฐ
./images/loader.png
์ ๊ฐ์ ์ ๋ ๊ฒฝ๋ก๋ผ๋ฉด ์๋ ๋ชจ๋ ์์ฒญ(require(./images/loader.png)
)์ผ๋ก ์ปดํ์ผ๋๊ณ ํ์ผ ์์คํ ์ ํด๋ ๊ตฌ์กฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํด๊ฒฐ๋ฉ๋๋ค. - ~๋ก ์์ํ๋ ๊ฒฝ๋ก(URLs starts with ~ symbol): ๋ง์ฝ URL ๊ฒฝ๋ก๊ฐ
./some-node-package/loader.png
์ ๊ฐ์~
๋ก ์์๋๋ค๋ฉด, ๋ชจ๋ ์์ฒญ์ผ๋ก ์ปดํ์ผ๋ฉ๋๋ค. - @๋ก ์์ํ๋ ๊ฒฝ๋ก(URLs starts with @ symbol): ๋ง์ฝ URL ๊ฒฝ๋ก๊ฐ
@
๋ก ์์๋๋ค๋ฉด, ๋ชจ๋ ์์ฒญ์ผ๋ก ์ปดํ์ผ๋ฉ๋๋ค. ์ด๊ฒ์ ์นํฉconfig
์@
์ ๋ํ ๋ณ์นญ์ด ์๋ ๊ฒฝ์ฐ์ ์ ์ฉํ๋ฉฐ, ๊ธฐ๋ณธ์ ์ผ๋กvue-cli
๊ฐ ๋ง๋ ๋ชจ๋ ํ๋ก์ ํธ์์/src
๋ฅผ ๊ฐ๋ฆฌํต๋๋ค.
- ์ ๋ ๊ฒฝ๋ก(Absolute path): ๋ง์ฝ URL ๊ฒฝ๋ก๊ฐ
-
Vue-loader
๋ ์์ฑ๋ ์ธ์ด ๋ธ๋ก์lang
์์ฑ์ ์ด์ฉํด ์ ์ ํ ๋ก๋๋ฅผ ์ ์ฉํ๊ณ ์นํฉ ์ค์ ์ ์ ์ฉ๋ ๊ท์น์ ๋ฐ๋ฆ ๋๋ค.Vue-loader
์์ SASS, LESS, Stylus๋ PostCSS ๊ฐ์ ์ ์ฒ๋ฆฌ๊ธฐ๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. -
๋ฒ์๋ฅผ ๊ฐ์ง๋ CSS(Scoped CSS)๋ Vue์์ ์ฑ๊ธ ํ์ผ ์ปดํฌ๋ํธ์์ ํด๋น ์ปดํฌ๋ํธ์ ์์ฑ๋ CSS๊ฐ ๋ค๋ฅธ ์ปดํฌ๋ํธ์ ์ํฅ์ ๋ฏธ์น์ง ์๋๋ก ๊ทธ ์ ์ฉ ๋ฒ์๋ฅผ ์ ํํ๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค. ์ฆ,
<style>
ํ๊ทธ๊ฐscoped
์์ฑ์ ๊ฐ์ง๊ณ ์๋ค๋ฉด, ํด๋น CSS๋ ํด๋น ์ปดํฌ๋ํธ์์๋ง ์ํฅ์ ๋ฏธ์นฉ๋๋ค.<style scoped> .greeting { color: green; } </style>
<template> <div class="greeting">Let's start Scoped CSS</div> </template>
The above code will be converted to plain CSS,
<style scoped> .greeting[data-v-f3f3eg9] { color: green; } </style>
<template> <div class="greeting" data-v-f3f3eg9>Let's start Scoped CSS</div> </template>
-
๋ฒ์๊ฐ ์ง์ ๋ ์คํ์ผ๊ณผ ๋ฒ์๊ฐ ์ง์ ๋์ง ์์ ์คํ์ผ์ ๋์ผํ ์ปดํฌ๋ํธ์ ํฌํจํ ์ ์์ต๋๋ค.
<style> /* global styles */ </style> <style scoped> /* local styles */ </style>
-
๋ฒ์๊ฐ ์ง์ ๋ CSS์์, ์์ ์ปดํฌ๋ํธ์ ์ํฅ์ ๋ฏธ์น๊ฒ ํ๋ ๋ฐฉ๋ฒ์
>>>
์ฐ์ฐ์๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค.<style scoped> .class1 >>> .class2 { /* ... */ } </style>
์์ CSS๋ ์๋๋ก ์ปดํ์ผ๋ฉ๋๋ค.
.class1[data-v-f3f3eg9] .class2 { /* ... */ }
Note: SASS์์๋
>>>
์ฐ์ฐ์๊ฐ ์ ๋๋ก ์๋ํ์ง ์์ ์ ์์ต๋๋ค. ์ด๋๋/deep/
๋๋::v-deep
์ ํ์๋ฅผ ๋์ ์ด์ฉํฉ๋๋ค. -
The parent component's styles will not leak into child components. But a child component's root node will be affected by both the parent's scoped CSS and the child's scoped CSS. i.e, your child component's root element has a class that also exists in the parent component, the parent component's styles will leak to the child. Anyway this is by design so that the parent can style the child root element for layout purposes. For example, the background color property of parent component leaked into child component as below, //parent.vue
<template> <div class="wrapper"> <p>parent</p> <ChildMessageComponent/> </div> </template> <script> import ChildMessageComponent from "./components/child"; export default { name: "App", components: { ChildMessageComponent } }; </script> <style scoped> .wrapper { background: blue; } </style>
//child.vue
<template> <div class="wrapper"> <p>child</p> </div> </template> <script> export default { name: "Hello, Scoped CSS", }; </script> <style scoped> .wrapper { background: red; } </style>
Now the background color of child wrapper is going to be blue instead red.
-
The scoped css style doesn't impact v-html directive's dynamically generated content. In this case, you can use deep selectors to solve this styling issue.
-
Yes, vue-loader provides first-class integration with CSS Modules as an alternative for simulated scoped CSS.
-
No, templates (or any Vue-specific HTML) are ONLY allowed in .vue files and render functions are required in other cases.
-
Below are the steps to use css modules in VueJS,
- ** Enable CSS modules:** CSS Modules must be enabled by passing modules: true option to css-loader
// webpack.config.js { module: { rules: [ // ... other rules omitted { test: /\.css$/, use: [ 'vue-style-loader', { loader: 'css-loader', options: { // enable CSS Modules modules: true, // customize generated class names localIdentName: '[local]_[hash:base64:8]' } } ] } ] } }
- ** Add module attribute:** Add the module attribute to your
<style>
<style module> .customStyle { background: blue; } </style>
- ** Inject CSS modules:** You can inject CSS modules object with computed property $style
<template> <div :class="$style.blue"> Background color should be in blue </p> </template>
It can work with object/array syntax of :class binding.
-
Yes,You can use preprocessors with CSS Modules. For example, sass-loader can configured in webpack file for sass preprocessor.
// webpack.config.js -> module.rules { test: /\.scss$/, use: [ 'vue-style-loader', { loader: 'css-loader', options: { modules: true } }, 'sass-loader' ] }
-
You can customize the name of the injected computed property by giving the module attribute a value. This will be helpful to avoid overwriting injected styled if you have more than one
<style>
tags in a single *.vue component. For example, you can use module attribute as below,<style module="a"> /* identifiers injected as a */ </style> <style module="b"> /* identifiers injected as b */ </style>
-
Hot reloading is not about reloading the page when you edit any .vue file. Instead, when you edit a *.vue file, all instances of that component will be swapped in without reloading the page. It improves the development experience when you are tweaking the templates or styling of your components.
-
Hot Reload is always enabled except below situations:
- webpack target is node (SSR)
- webpack minifies the code
- process.env.NODE_ENV === 'production'
-
You can use
hotReload: false
option to disable the Hot Reload explicitly. It can be configured as below,module: { rules: [ { test: /\.vue$/, loader: 'vue-loader', options: { hotReload: false // disables Hot Reload } } ] }
-
The vue loader plugin internally uses hot reloading. If you are scaffolding project with
vue-cli
, hot reloading comes out of the box but if you are manually setting up the project then hot reloading is enabled by default withwebpack-dev-server --hot
command. -
Below are the state preservation rules in hot reloading, 1.When editing the
<template>
of a component, instances of the edited component will re-render in place, preserving all current private state. 2.When editing the<script>
part of a component, instances of the edited component will be destroyed and re-created in place. 3.When editing the<style>
hot reload operates on its own via vue-style-loader without affecting application state. -
You can create functional components by adding functional attribute to template block,
<template functional> <div>{{ props.msg }}</div> </template>
-
If you need to access properties defined globally on
Vue.prototype
then you can access them on parent,<template functional> <div>{{ parent.$someProperty }}</div> </template>
-
You can perform testing in two ways,
- ** Using vue-cli:** It offers pre-configured unit testing and e2e testing setups
- ** Manual setup:** You can manually setting up unit tests for *.vue files using either mocha-webpack or jest
-
The stylelint linter supports linting style parts of Vue single file components. You can run linter on particular vue file as below
stylelint MyComponent.vue
Other option is configuring stylelint-webpack-plugin in webpack. It can be configured as a dev dependency.
// webpack.config.js const StyleLintPlugin = require('stylelint-webpack-plugin'); module.exports = { // ... other options plugins: [ new StyleLintPlugin({ files: ['**/*.{vue,htm,html,css,sss,less,scss,sass}'], }) ] }
-
The official
eslint-plugin-vue
supports linting both the template and script parts of Vue single file components. You can configure plugin in your ESLint config,// .eslintrc.js module.exports = { extends: [ "plugin:vue/essential" ] }
You can run linter on particular component as below,
eslint --ext js,vue MyComponent.vue
-
You can use
eslint-loader
for *.vue files in order to automatically linted on save during development. It can be installed as npm module,npm install -D eslint eslint-loader
After that you need to add it as pre-loader,
// webpack.config.js module.exports = { // ... other options module: { rules: [ { enforce: 'pre', test: /\.(js|vue)$/, loader: 'eslint-loader', exclude: /node_modules/ } ] } }
-
CSS Extraction is used to extract all the processed CSS in all Vue components into a single CSS file. For webpack4, you need to install below npm command,
npm install -D mini-css-extract-plugin
You can configure this plugin in webpack as below,
// webpack.config.js var MiniCssExtractPlugin = require('mini-css-extract-plugin') module.exports = { // other options... module: { rules: [ // ... other rules omitted { test: /\.css$/, use: [ process.env.NODE_ENV !== 'production' ? 'vue-style-loader' : MiniCssExtractPlugin.loader, 'css-loader' ] } ] }, plugins: [ // ... Vue Loader plugin omitted new MiniCssExtractPlugin({ filename: 'style.css' }) ] }
-
You can define custom language blocks inside *.vue files based on the
lang
attribute of the block, the block's tag name, and the rules in your webpack config. You can also useresourceQuery
to match a rule against a custom block with no lang. For example, to match against custom blocks.{ module: { rules: [ { resourceQuery: /blockType=message/, loader: 'loader-to-use' } ] } }
-
Below are the list of major stylelint features
- It has more than 160 built-in rules to catch errors, apply limits and enforce stylistic conventions
- Understands latest CSS syntax including custom properties and level 4 selectors
- It extracts embedded styles from HTML, markdown and CSS-in-JS object & template literals
- Parses CSS-like syntaxes like SCSS, Sass, Less and SugarSS
- Supports Plugins for reusing community plugins and creating own plugins
-
Vuex enforces below rules to structure any application.
- Application-level state is centralized in the store.
- The only way to mutate the state is by committing mutations, which are synchronous transactions.
- Asynchronous logic should be encapsulated in, and can be composed with actions. The project structure for any non-trivial application would be as below,
-
Yes, Vuex supports hot-reloading for mutations, modules, actions and getters during development. You need to use either webpack's hot module replacement API or browserify's hot module replacement plugin.
-
The store.hotUpdate() API method is used for mutations and modules. For example, you need to configure vuex store as below,
// store.js import Vue from 'vue' import Vuex from 'vuex' import mutations from './mutations' import myModule from './modules/myModule' Vue.use(Vuex) const state = { message: "Welcome to hot reloading" } const store = new Vuex.Store({ state, mutations, modules: { moduleA: myModule } }) if (module.hot) { // accept actions and mutations as hot modules module.hot.accept(['./mutations', './modules/newMyModule'], () => { // Get the updated modules const newMutations = require('./mutations').default const newMyModule = require('./modules/myModule').default //swap in the new modules and mutations store.hotUpdate({ mutations: newMutations, modules: { moduleA: newMyModule } }) }) }
-
Since mutations are just functions that completely rely on their arguments it will be easier to test. You need to keep mutations inside your store.js file and should also export the mutations as a named export apart from default export. Let's take an example of increment mutations,
// mutations.js export const mutations = { increment: state => state.counter++ }
And test them using mocha and chai as below,
// mutations.spec.js import { expect } from 'chai' import { mutations } from './store' // destructure assign `mutations` const { increment } = mutations describe('mutations', () => { it('INCREMENT', () => { // mock state const state = { counter: 10 } // apply mutation increment(state) // assert result expect(state.counter).to.equal(11) }) })
-
It is easier to test getters similar to mutations. It is recommended to test these getters if they have complicated computation. Let's take a simple todo filter as a getter
// getters.js export const getters = { filterTodos (state, status) { return state.todos.filter(todo => { return todo.status === status }) } }
And the test case for above getter as follows,
// getters.spec.js import { expect } from 'chai' import { getters } from './getters' describe('getters', () => { it('filteredTodos', () => { // mock state const state = { todos: [ { id: 1, title: 'design', status: 'Completed' }, { id: 2, title: 'testing', status: 'InProgress' }, { id: 3, title: 'development', status: 'Completed' } ] } // mock getter const filterStatus = 'Completed' // get the result from the getter const result = getters.filterTodos(state, filterStatus) // assert the result expect(result).to.deep.equal([ { id: 1, title: 'design', status: 'Completed' }, { id: 2, title: 'development', status: 'Completed' } ]) }) })
-
By proper mocking, you can bundle tests with webpack and run them on node without having depenceny on Browser API. It involves 2 steps,
- Create webpack config: Create webpack config with proper .babelrc
// webpack.config.js module.exports = { entry: './test.js', output: { path: __dirname, filename: 'test-bundle.js' }, module: { loaders: [ { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ } ] } }
- ** Run testcases:** First you need to bundle and then run them using mocha as below,
webpack mocha test-bundle.js
-
Below are the steps to run tests in real browser,
- Install
mocha-loader
. - Configure webpack config entry point to 'mocha-loader!babel-loader!./test.js'.
- Start webpack-dev-server using the config.
- Go to localhost:8080/webpack-dev-server/test-bundle to see the test result
- Install
-
In strict mode, whenever Vuex state is mutated outside of mutation handlers, an error will be thrown. It make sure that all state mutations can be explicitly tracked by debugging tools. You can just enable this by passing
strict: true
while creating the vuex store.const store = new Vuex.Store({ // ... strict: true })
-
No, it is not recommended to use strict mode in production environment. Strict mode runs a synchronous deep watcher on the state tree for detecting inappropriate mutations and it can be quite expensive when you perform large amount of mutations. i.e, It can impact performance if you enable in production mode. Hence it should be handled through build tools,
const store = new Vuex.Store({ // ... strict: process.env.NODE_ENV !== 'production' })
-
The vuex plugin is an option hat exposes hooks for each mutation. It is a normal function that receives the store as the only argument. You can create your own plugin or use built-in plugins. The plugin skeleton would be as below,
const myPlugin = store => { // called when the store is initialized store.subscribe((mutation, state) => { // called after every mutation. // The mutation comes in the format of `{ type, payload }`. }) }
After that plugin can be configured for plugins options as below,
const store = new Vuex.Store({ // ... plugins: [myPlugin] })
-
Similar to components you can't mutate state directly but they can trigger changes by by committing mutations. This way a plugin can be used to sync a data source to the store. For example, createWebSocketPlugin plugin is used to sync a websocket data source to the store.
export default function createWebSocketPlugin (socket) { return store => { socket.on('data', data => { store.commit('receiveData', data) }) store.subscribe(mutation => { if (mutation.type === 'UPDATE_DATA') { socket.emit('update', mutation.payload) } }) } }
And then configure plugin in vuex store as below
const plugin = createWebSocketPlugin(socket) const store = new Vuex.Store({ state, mutations, plugins: [plugin] })
-
A Vuex "store" is basically a container that holds your application state. The store creation is pretty straightforward. Below are the list of instructions to use vuex in an increment application,
- Configure vuex in vuejs ecosystem
import Vuex from "vuex"; Vue.use(Vuex)
- Provide an initial state object and some mutations
// Make sure to call Vue.use(Vuex) first if using a module system const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } } })
- Trigger state change with commit and access state variables,
store.commit('increment') console.log(store.state.count) // -> 1
-
Below are the two major differences between vuex store and plain global object
- Vuex stores are reactive: If the store's state changes then vue components will reactively and efficiently get updated
- Cannot directly mutate the store's state: The store's state is changed by explicitly committing mutations to ensure that every state change leaves a track-able record for tooling purpose
-
We want to explicitly track application state in order to implement tools that can log every mutation, take state snapshots, or even perform time travel debugging. So we need to commit a mutation instead of changing store's state directly.
-
Vuex's single state tree is single object contains all your application level state and serves as the "single source of truth". It does not conflict with modularity when you split state and mutations into sub modules.
-
You can install vuex using npm or yarn as below,
npm install vuex --save (or) yarn add vuex
In a module system, you must explicitly install Vuex via Vue.use()
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex)
(OR) You can also install it using CDN links such as unpkg.cpm which provides NPM-based CDN links. Just include vuex after Vue and it will install itself automatically.
<script src="https://unpkg.com/vue.js"></script> <script src="https://unpkg.com/vuex.js"></script>
Note: You can use a specific version/tag via URLs like https://unpkg.com/vuex@2.0.0. If you don't mention any version then it will point to latest version.
-
Yes, Vuex requires Promise. If your supporting browsers do not implement Promise (e.g. IE), you can use a polyfill library, such as es6-promise using npm or yarn.
npm install es6-promise --save # NPM yarn add es6-promise # Yarn
After that import into anywhere in your application,
import 'es6-promise/auto'
-
Since Vuex stores are reactive, you can retrieve" state from store by simply returning store's state from within a computed property. i.e, Whenever store state changes, it will cause the computed property to re-evaluate, and trigger associated DOM updates. Let's take a hello word component which display store's state in the template,
// let's create a hello world component const Greeting = { template: `<div>{{ greet }}</div>`, computed: { greet () { return store.state.msg } } }
-
Vuex provides a mechanism to "inject" the store into all child components from the root component with the store option. It will be enabled by vue.use(vuex). For example, let's inject into our app component as below,
const app = new Vue({ el: '#app', // provide the store using the "store" option. // this will inject the store instance to all child components. store, components: { Greeting }, template: ` <div class="app"> <greeting></greeting> </div> ` })
Now the store will be injected into all child components of the root and will be available on them as this.$store
// let's create a hello world component const Greeting = { template: `<div>{{ greet }}</div>`, computed: { greet () { return this.$store.state.msg } } }
-
In Vuex application, creating a computed property every time whenever we want to access the store's state property or getter is going to be repetitive and verbose, especially if a component needs more than one state property. In this case, we can make use of the mapState helper of vuex which generates computed getter functions for us. Let's take an increment example to demonstrate mapState helper,
// in full builds helpers are exposed as Vuex.mapState import { mapState } from 'vuex' export default { // ... computed: mapState({ // arrow functions can make the code very succinct! username: state => state.username, // passing the string value 'username' is same as `state => state.username` usernameAlias: 'username', // to access local state with `this`, a normal function must be used greeting (state) { return this.localTitle + state.username } }) }
We can also pass a string array to mapState when the name of a mapped computed property is the same as a state sub tree name
computed: mapState([ // map this.username to store.state.username 'username' ])
-
You can use object spread operator syntax in order to combine mapState helper(which returns an object) with other local computed properties. This way it simplify merging techniques using utilities.
computed: { localComputed () { /* ... */ }, // mix this into the outer object with the object spread operator ...mapState({ // ... }) }
-
No, if a piece of state strictly belongs to a single component, it could be just fine leaving it as local state. i.e, Eventhough vuex used in the application, it doesn't mean that you need to keep all the local state in vuex store. Other the code becomes more verbose and indirect although it makes your state mutations more explicit and debuggable.
-
Vuex getters acts as computed properties for stores to compute derived state based on store state. Similar to computed properties, a getter's result is cached based on its dependencies, and will only re-evaluate when some of its dependencies have changed. Let's take a todo example which as completedTodos getter to find all completed todos,
const store = new Vuex.Store({ state: { todos: [ { id: 1, text: 'Vue course', completed: true }, { id: 2, text: 'Vuex course', completed: false }, { id: 2, text: 'Vue Router course', completed: true } ] }, getters: { completedTodos: state => { return state.todos.filter(todo => todo.completed) } } })
**Note:**Getters receive state as first argument.
-
You can access values of store's getter object(store.getters) as properties. This is known as property style access. For example, you can access todo's status as a property,
store.getters.todosStatus
The getters can be passed as 2nd argument for other getters. For example, you can derive completed todo's count based on their status as below,
getters: { completedTodosCount: (state, getters) => { return getters.todosStatus === 'completed' } }
Note: The getters accessed as properties are cached as part of Vue's reactivity system.
-
You can access store's state in a method style by passing arguments. For example, you can pass user id to find user profile information as below,
getters: { getUserProfileById: (state) => (id) => { return state.users.find(user => user.id === id) } }
After that you can access it as a method call,
store.getters.getUserProfileById(111); {id: '111', name: 'John', age: 33}
-
The mapGetters is a helper that simply maps store getters to local computed properties. For example, the usage of getters for todo app would be as below,
import { mapGetters } from 'vuex' export default { computed: { // mix the getters into computed with object spread operator ...mapGetters([ 'completedTodos', 'todosCount', // ... ]) } }
-
Vuex mutations are similar to any events with a string
type
and ahandler
. The handler function is where we perform actual state modifications, and it will receive the state as the first argument. For example, the counter example with increment mutation would be as below,const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { // mutate state state.count++ } } })
You can't directly invoke mutation instead you need to call
store.commit
with its type. The above mutation would be triggered as folowsstore.commit('increment')
-
You can also pass payload for the mutation as an additional argument to
store.commit
. For example, the counter mutation with payload object would be as below,mutations: { increment (state, payload) { state.count += payload.increment } }
And then you can trigger increment commit
store.commit('increment', { increment: 20 })
Note: You can also pass primitives as payload.
-
You can also commit a mutation is by directly using an object that has a type property.
store.commit({ type: 'increment', value: 20 })
Now the entire object will be passed as the payload to mutation handlers(i.e, without any changes to handler signature).
mutations: { increment (state, payload) { state.count += payload.value } }
-
Since a Vuex store's state is made reactive by Vue, the same reactivity caveats of vue will apply to vuex mutations. These are the rules should be followed for vuex mutations,
- It is recommended to initialize store's initial state with all desired fields upfront
- Add new properties to state Object either by set method or object spread syntax
Vue.set(stateObject, 'newProperty', 'John')
(OR)
state.stateObject = { ...state.stateObject, newProperty: 'John' }
-
You need to remember that mutation handler functions must be synchronous. This is why because any state mutation performed in the callback is essentially un-trackable. It is going to be problematic when the devtool will need to capture a "before" and "after" snapshots of the state during the mutations.
mutations: { someMutation (state) { api.callAsyncMethod(() => { state.count++ }) } }
-
You can commit mutations in components with either this.$store.commit('mutation name') or mapMutations helper to map component methods to store.commit calls. For example, the usage of mapMutations helper on counter example would be as below,
import { mapMutations } from 'vuex' export default { methods: { ...mapMutations([ 'increment', // map `this.increment()` to `this.$store.commit('increment')` // `mapMutations` also supports payloads: 'incrementBy' // map `this.incrementBy(amount)` to `this.$store.commit('incrementBy', amount)` ]), ...mapMutations({ add: 'increment' // map `this.add()` to `this.$store.commit('increment')` }) } }
-
No, it is not mandatory. But you might observed that State management implementations such Flux and Redux use constants for mutation types. This convention is just a preference and useful to take advantage of tooling like linters, and putting all constants in a single file allows your collaborators to get an at-a-glance view of what mutations are possible in the entire application. For example, the mutations can be declared as below,
// mutation-types.js export const SOME_MUTATION = 'SOME_MUTATION'
And you can configure them in store as follows,
// store.js import Vuex from 'vuex' import { SOME_MUTATION } from './mutation-types' const store = new Vuex.Store({ state: { ... }, mutations: { // ES2015 computed property name feature to use a constant as the function name [SOME_MUTATION] (state) { // mutate state } } })
-
In Vuex, mutations are synchronous transactions. But if you want to handle asynchronous operations then you should use actions.
-
Actions are similar to mutations, but there are two main differences,
- Mutations perform mutations on the state, actions commit mutations.
- Actions can contain arbitrary asynchronous operations unlike mutations.