vue.js進階之元件
因為之前的專案用了vue,但是是邊學邊用,很多細節都不熟悉,比如vue-router,之前也寫過vue+browserify構建大型應用。這次寫一個vue最強大的功能,就是vue的元件。
首先推薦一個部落格,真的寫得非常好,當然官方API也寫得很好。
http://www.cnblogs.com/keepfool/p/5625583.html
vue元件基本步驟
這個圖也是盜用這個部落格的,所以先宣告一下,我算是轉載吧,哈哈。
步驟:
1 、建立元件構造器
2、 註冊元件
3、在Vue例項中使用元件
一個小小的demo來說明一下,這個就是官方的例子:
<div id="example">
<my-component></my-component>
</div>
// 定義
var MyComponent = Vue.extend({
template: '<div>A custom component!</div>'
})
// 註冊
Vue.component('my-component', MyComponent)
// 建立根例項
new Vue({
el: '#example'
})
<div id="example">
<div>A custom component!</div >
</div>
理解
- Vue.extend()是Vue構造器的擴充套件,呼叫Vue.extend()建立的是一個元件構造器。
- Vue.extend()構造器有一個選項物件,選項物件的template屬性用於定義元件要渲染的HTML。
- 使用Vue.component()註冊元件時,需要提供2個引數,第1個引數時元件的標籤,第2個引數是元件構造器。
- 元件應該掛載到某個Vue例項下,否則它不會生效。
屬性值的傳遞
元件例項的作用域是孤立的。這意味著不能並且不應該在子元件的模板內直接引用父元件的資料。可以使用 props 把資料傳給子元件。
Vue.component('child' , {
// 宣告 props
props: ['message'],
// 就像 data 一樣,prop 可以用在模板內
// 同樣也可以在 vm 例項中像 “this.message” 這樣使用
template: '<span>{{ message }}</span>'
})
<child message="hello!"></child>
官網的例子。
然而如何繫結到style和id呢?
data和props
1、data
使用元件Components時,大多數選項可以被傳入到 Vue 構造器中,有一個例外: data 必須是函式。因為如果不是函式的,宣告多個元件的時候,他們共享的就是同一個data,這樣就會亂掉。如果通過函式返回,那麼每個元件維持自己的data作用域。該data屬性只在其component中可見。
<body>
<div id="app">
<my-component>
</my-component>
</div>
<template id="myComponent">
<div>
<h2>{{msg}}</h2>
<button @click="showMsg">Show Message</button>
</div>
</template>
</body>
<script type="text/javascript" src="../dist/vue.js"></script>
<script type="text/javascript" src="../dist/vue-router.js"></script>
<script type="text/javascript">
new Vue({
el: '#app',
components: {
'my-component': {
template: '#myComponent',
data: function() {
return {
msg: 'This is a Component!' //Vue中component的data必須通過function() return
}
},
methods: {
showMsg: function() {
alert(this.msg);
}
}
}
}
})
</script>
Component其他感覺和之前用的Vue沒什麼區別。
也可以有methods方法,有data,多了props
2、關於元件的作用域
template不是標準的HTML元素,瀏覽器是不理解這個元素的。
這裡還可以看到template
那麼Vue是如何讓瀏覽器理解template標籤的呢:
感覺必須是掛載在Vue中,通過Vue解析出這個標籤成為瀏覽器可以理解的元素。
就像下面這段程式碼,必須新new 一個Vue,Vue使用myComponent作為components,然後在Vue繫結的app中,這個component就會被解析成瀏覽器可以閱讀的語言。
new Vue({
el: '#app',
components: {
'my-component': {
template: '#myComponent',
data: function() {
return {
msg: 'This is a Component!' //Vue中component的data必須通過function() return
}
},
methods: {
showMsg: function() {
alert(this.msg);
}
}
}
}
})
並且vue例項和component的作用域是獨立的
new Vue({
el: '#app',
data:{
display:true //vue例項的display
},
components: {
'my-component': {
template: '#myComponent',
data: function() {
return {
msg: 'This is a Component!',
display: false //component中的display
}
},
methods: {
showMsg: function() {
alert(this.msg);
}
}
}
}
})
<div id="app">
<my-component v-show="display">
</my-component>
</div>
<template id="myComponent">
<div>
<h2 v-show="display">{{msg}}</h2>
<button @click="showMsg">Show Message</button>
</div>
</template>
執行結果:
h2被隱藏了,
但是my-component沒有被隱藏。
也就是說:父元件模板的內容在父元件作用域內編譯;子元件模板的內容在子元件作用域內編譯
那麼父子元件如何進行通訊呢?
答案是props
3、父元件和子元件通訊
官網的一張圖
父元件通過* props* 向下傳遞資料給子元件,子元件通過 events 給父元件傳送訊息。看看它們是怎麼工作的。
a. 父元素向子元素通訊:props
還是剛剛的例子,我們在component中添加了props
例子1:靜態props
new Vue({
el: '#app',
data:{
display:true
},
components: {
'my-component': {
template: '#myComponent',
props:['parentmsg'], //宣告props
data: function() {
return {
msg: 'This is a Component!',
display: false //Vue中component的data必須通過function() return
}
},
methods: {
showMsg: function() {
alert(this.msg);
}
}
}
}
})
我們在template中加了一個p,這個p引用父元素的msg,另外說一聲Vue中屬性繫結這個不識別大小寫
<my-component v-show="display" parentMsg="ParentMsg">
在瀏覽器看到就是
<my-component v-show="display" parentmsg="ParentMsg">
所以即使你聲明瞭props:[‘parentMsg’]也顯示不出來。我的程式碼是這樣
後來看到官方的解釋是這樣的:
HTML 特性不區分大小寫。當使用非字串模版時,prop的名字形式會從 camelCase 轉為
kebab-case(短橫線隔開)
<div id="app">
<my-component v-show="display" parentmsg="ParentMsg">
</my-component>
</div>
<template id="myComponent">
<div>
<h2 v-show="display">{{msg}}</h2>
<p>{{parentmsg}}</p>
<button @click="showMsg">Show Message</button>
</div>
</template>
執行結果
這裡就是父元素的ParentMsg傳遞給了component
例子2:動態props
感覺神奇之處就在這裡,用v-bind把剛剛的props繫結起來
<input type="" name="" v-model="ParentMsg">
<my-component v-show="display" v-bind:parentmsg="ParentMsg">
</my-component>
</div>
這樣ParentMsg就根據輸入框中的ParentMsg動態變化了。
new Vue({
el: '#app',
data:{
display:true,
ParentMsg:"Hello This is Parent"
},
components: {
'my-component': {
template: '#myComponent',
props:['parentmsg'],
data: function() {
return {
msg: 'This is a Component!',
display: false //Vue中component的data必須通過function() return
}
},
methods: {
showMsg: function() {
alert(this.msg);
}
}
}
}
})
prop 是單向繫結的:當父元件的屬性變化時,將傳導給子元件,但是不會反過來。這是為了防止子元件無意修改了父元件的狀態——這會讓應用的資料流難以理解。
另外,每次父元件更新時,子元件的所有 prop 都會更新為最新值。這意味著你不應該在子元件內部改變 prop 。如果你這麼做了,Vue 會在控制檯給出警告。
通常有兩種改變 prop 的情況:
prop 作為初始值傳入,子元件之後只是將它的初始值作為本地資料的初始值使用;
prop 作為需要被轉變的原始值傳入。
<div id="app">
<input type="" name="" v-model="ParentMsg">
<my-component v-show="display" v-bind:parentmsg="ParentMsg">
</my-component>
</div>
<template id="myComponent">
<div>
<h2 v-show="display">{{msg}}</h2>
<p>{{parentmsg}}</p>
<p>{{childprops}}</p>
<button @click="showMsg">Show Message</button>
</div>
</template>
new Vue({
el: '#app',
data:{
display:true,
ParentMsg:"Hello This is Parent"
},
components: {
'my-component': {
template: '#myComponent',
props:['parentmsg'],
data: function() {
return {
msg: 'This is a Component!',
childprops:"child:"+this.parentmsg, //可以在data中獲取props,並生成新的data
display: false //Vue中component的data必須通過function() return
}
},
methods: {
showMsg: function() {
alert(this.msg);
}
}
}
}
})
a. 子元素向父元素傳遞資訊:自定義事件
我們知道,父元件是使用 props 傳遞資料給子元件,但如果子元件要把資料傳遞回去,應該怎樣做?那就是自定義事件!
使用 $on(eventName) 監聽事件
使用 $emit(eventName) 觸發事件
<button-counter v-on:increment="incrementTotal"></button-counter>
Vue.component('button-counter', {
template: '<button v-on:click="increment">{{counter}}</button>',
data: function() {
return {
counter: 0
}
},
methods: {
increment: function() {
alert("increment")
this.counter += 1;
this.$emit('increment');
}
}
})
new Vue({
el: '#app',
data: {
display: true,
ParentMsg: "Hello This is Parent",
total: 0
},
methods: {
incrementTotal: function() {
alert("incrementTotal")
this.total += 1;
}
},
})
先執行increment,再執行incrementTotal
感覺這兩句是非常重要的:
v-on:increment="incrementTotal"
this.$emit('increment');
分析,button上面綁定了v-on:click=”increment”,當點選按鈕觸發increment事件,當increment函式執行完畢,觸發incrementTotal函式。全在於 this.$emit(‘increment’); 不然執行完畢increment就完畢了。
官網的例子:
<div id="counter-event-example">
<p>{{ total }}</p>
<button-counter v-on:increment="incrementTotal"></button-counter>
<button-counter v-on:increment="incrementTotal"></button-counter>
</div>
Vue.component('button-counter', {
template: '<button v-on:click="increment">{{ counter }}</button>',
data: function () {
return {
counter: 0
}
},
methods: {
increment: function () {
this.counter += 1
this.$emit('increment')
}
},
})
new Vue({
el: '#counter-event-example',
data: {
total: 0
},
methods: {
incrementTotal: function () {
this.total += 1
}
}
})
這樣點選任意一個button都會呼叫 incrementTotal。
input v-model="something">
其實v-model僅僅是一顆語法糖
<input v-bind:value="something" v-on:input="something = $event.target.value">
所以要讓元件的 v-model 生效,它必須:
1.接受一個 value 屬性
2. 在有新的 value 時觸發 input 事件
<div id="app2">
<currency-input label="Price" v-model="price"></currency-input>
<currency-input label="Shipping" v-model="shipping"></currency-input>
<currency-input label="Handling" v-model="handling"></currency-input>
<currency-input label="Discount" v-model="discount"></currency-input>
<p>Total: ${{ total }}</p>
</div>
<template id="myComponent">
<div>
<h2 v-show="display">{{msg}}</h2>
<p>{{parentmsg}}</p>
<p>{{childprops}}</p>
<button @click="showMsg">Show Message</button>
</div>
</template>
Vue.component('currency-input', {
template: '\
<div>\
<label v-if="label">{{ label }}</label>\
$\
<input\
ref="input"\
v-bind:value="value"\
v-on:input="updateValue($event.target.value)"\
v-on:focus="selectAll"\
>\
</div>\
',
props: {
value: {
// type: Number,
default: 0
},
label: {
// type: String,
default: ''
}
},
methods: {
updateValue: function (value) {
this.$emit('input', value) //觸發input事件
},
selectAll: function (event) {
setTimeout(function () {
event.target.select()
}, 0)
}
}
})
new Vue({
el: '#app2',
data: {
price: 0,
shipping: 0,
handling: 0,
discount: 0
},
computed: {
total: function () {
return ((
this.price * 100 +
this.shipping * 100 +
this.handling * 100 -
this.discount * 100
) / 100).toFixed(2)
}
}
})