通過Vue在項目中的應(yīng)用氛堕,我發(fā)現(xiàn)它的雙向綁定機制對于表單異步請求十分頻繁的項目十分受用馏臭,因為相當(dāng)于輸入綁定在了變量域中,可以直接對它的data域進行提取讼稚。而且如果將Ajax請求設(shè)為同步的括儒,之后可以直接使用請求完畢的數(shù)據(jù)設(shè)為Vue實例的data域,還是十分方便的锐想。但是過程中也發(fā)現(xiàn)了帮寻,在Vue實例中,有時想嵌入其他的Vue實例赠摇,卻是做不到固逗,就想趁著不忙了把組件這塊補上浅蚪。
組件的定義
如下
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times. </button>'
});
可以看到組件的基本結(jié)構(gòu)分為data和template兩種膝昆,類似于一個完整的Vue實例(只不過沒有el這個屬性)痊土,但是其中的data返回的是一個函數(shù)曹步,如果不寫成函數(shù)的話剃执,則JS引擎每次初始化一個組件蜓萄,他們的data域就會指向同一個引用幻赚,造成事實上的耦合甩栈。
而template則類似于React的JSX代碼伙窃,使用HTML文本片來書寫組件真實的模板HTML代碼饿这,配合合適的IDE浊伙,在其中也可以做到自動補全。
我們來實例化一個Vue對象长捧。
<div id="components-demo">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
<!-- 組件可以復(fù)用多次嚣鄙,每次復(fù)用代表創(chuàng)建一個新的Vue實例-->
</div>
var vm1 = new Vue({
el: '#components-demo'
});
頗有一絲XML的味道串结,目前小程序中稀奇古怪的標記語言也是基于XML構(gòu)建的哑子,在此不再贅述,在Vue實例裝載后肌割,會在渲染“button-counter”這個標記之前在Vue全局對象綁定的組件中找到對應(yīng)的組件卧蜓,如果沒有,則會拋出一個異常把敞,否則則會按照實例對象和組件中的data進行virtual dom的渲染弥奸。
組件的屬性
說白了類似于對函數(shù)的調(diào)用,傳入的參數(shù)奋早。這里的“參數(shù)”可以不止一個(方括號)盛霎,其中的字符串代表“參數(shù)名”。
// 為組件聲明屬性列表
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
});
不同的是耽装,標記語言元素的參數(shù)是以屬性給出的愤炸,可以猜想Vue其中封裝了一個高性能的XML解析器?
<div id="property-demo">
<blog-post title="hello"></blog-post>
<blog-post title="thank you"></blog-post>
<blog-post title="thank you very much"></blog-post>
</div>
實例化Vue對象掉奄。
var vm2 = new Vue({
el: '#property-demo'
});
效果也還不錯规个。
深入探討組件屬性
可能經(jīng)歷過HTML4時代的前端開發(fā)者,對于一些h4中的標記姓建,比如<font></font>之類的是深惡痛絕诞仓,雖然極大的降低了學(xué)習(xí)成本,但是一旦接手對于該項目的重構(gòu)速兔,改完可能手都要抽筋了
HTML元素可以使用v-bind綁定數(shù)據(jù)和屬性狂芋,自然組件也可以。
var vm3 = new Vue({
el: '#property-demo2',
data: {
posts: [
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
]
}
});
復(fù)用剛才的組件
<div id="property-demo2">
<blog-post v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
></blog-post>
</div>
結(jié)果是類似的憨栽,但內(nèi)容不同。
再聯(lián)想到剛才我提到的可以使用Ajax初始化Vue實例的data域,是不是又有了新的想法呢#手動滑稽屑柔。
與此同時屡萤,搭配v-html屬性,可以使模板的用法更加豐富掸宛。
Vue.component('blog-posture', {
props: ['post'],
template:
"<div class='blog-post'>" +
"<h3>{{ post.title }}</h3>" +
"<div v-html='post.content'></div>" +
"</div>"
})
var vm4 = new Vue({
el: '#property-demo3',
data: {
posts: [
{ id: 1, title: 'My journey with Vue' , content: '<div>123</div><p>4949494</p>'},
{ id: 2, title: 'Blogging with Vue' , content: '<img src=x onerror="javaScript:alert(1)>'},
{ id: 3, title: 'Why Vue is so fun' , content: '<textarea>1231231223123</textarea>'}
]
}
});
我們在posts數(shù)組中的數(shù)據(jù)里死陆,定義了一個content用來盛放html代碼(注意其中id為2的代碼,它并不會執(zhí)行)唧瘾,這樣content的內(nèi)容最終會渲染到template的div中措译。
HTML代碼:
<div id="property-demo3">
<blog-posture v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
v-bind:post="post"
></blog-posture>
</div>
最終結(jié)果是這樣的。
但是其中的xss代碼并沒有被執(zhí)行饰序,或者說那個img元素领虹,壓根沒被渲染。
控制臺中的結(jié)果證明了這個結(jié)論求豫。
組件的雙向綁定
首先這個讓我想起了v-model這個指令塌衰。
<input v-model="searchText">
它實質(zhì)上是一個語法糖,包含了v-bind和v-on:兩條指令的搭配蝠嘉,在處理輸入的雙向綁定的時候最疆,它等效于這兩個屬性的組合:
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
基于這個原理,我們也可以使用組件自定義輸入框(畢竟原生的確實是很丑)
Vue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`
});
注意
1.組件必須向外提供value屬性蚤告,且HTML代碼必須提供該屬性
2.在組件的template內(nèi)部努酸,使用v-bind和v-on組合的方式,將組件的value屬性綁定到template中輸入框的value屬性中杜恰,并且處理好v-on中的input事件获诈。
于是Vue實例就可以使用v-model來綁定實例中的數(shù)據(jù)。
var vm6 = new Vue({
el: '#component-input',
data: {
searchText: ""
}
})
<div id="component-input">
<custom-input
v-model="searchText"
></custom-input>
</div>
組件-實例通信
組件可以共享實例的全局變量箫章。
Vue.component('blog-position', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<button v-on:click="$emit('enlarge-text')">
Enlarge text
</button>
<div v-html="post.content"></div>
</div>
`
});
var vm5 = new Vue({
el: '#blog-posts-events-demo',
data: {
posts: [
{ id: 1, title: 'My journey with Vue' , content: '<div>123</div><p>4949494</p>'},
{ id: 2, title: 'Blogging with Vue' , content: '<img src=x onerror="javaScript:alert(1)>'},
{ id: 3, title: 'Why Vue is so fun' , content: '<textarea>1231231223123</textarea>'}
],
postFontSize: 1
}
})
vlist.push(vm5);
這里我們還用到了$emit方法烙荷,是處理自定義事件的。
<div id="blog-posts-events-demo">
<div :style="{ fontSize: postFontSize + 'em' }">
<blog-position
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
v-on:enlarge-text="postFontSize += 0.1"
></blog-position>
</div>
</div>
最終的結(jié)果如圖: