通过Vue在项目中的应用,我发现它的双向绑定机制对于表单异步请求十分频繁的项目十分受用,因为相当于输入绑定在了变量域中,可以直接对它的data域进行提取。而且如果将Ajax请求设为同步的,之后可以直接使用请求完毕的数据设为Vue实例的data域,还是十分方便的。但是过程中也发现了,在Vue实例中,有时想嵌入其他的Vue实例,却是做不到,就想趁着不忙了把组件这块补上。
组件的定义
如下
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times. </button>'
});
可以看到组件的基本结构分为data和template两种,类似于一个完整的Vue实例(只不过没有el这个属性),但是其中的data返回的是一个函数,如果不写成函数的话,则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>
<!-- 组件可以复用多次,每次复用代表创建一个新的Vue实例-->
</div>
var vm1 = new Vue({
el: '#components-demo'
});
最终的效果图,点击按钮之间的数据互不影响
颇有一丝XML的味道,目前小程序中稀奇古怪的标记语言也是基于XML构建的,在此不再赘述,在Vue实例装载后,会在渲染“button-counter”这个标记之前在Vue全局对象绑定的组件中找到对应的组件,如果没有,则会抛出一个异常,否则则会按照实例对象和组件中的data进行virtual dom的渲染。
组件的属性
说白了类似于对函数的调用,传入的参数。这里的“参数”可以不止一个(方括号),其中的字符串代表“参数名”。
// 为组件声明属性列表
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
});
不同的是,标记语言元素的参数是以属性给出的,可以猜想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'
});
效果也还不错。
效果
深入探讨组件属性
可能经历过HTML4时代的前端开发者,对于一些h4中的标记,比如<font></font>之类的是深恶痛绝,虽然极大的降低了学习成本,但是一旦接手对于该项目的重构,改完可能手都要抽筋了
HTML元素可以使用v-bind绑定数据和属性,自然组件也可以。
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' }
]
}
});
复用刚才的组件
<div id="property-demo2">
<blog-post v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
></blog-post>
</div>
结果是类似的,但内容不同。
结果
再联想到刚才我提到的可以使用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数组中的数据里,定义了一个content用来盛放html代码(注意其中id为2的代码,它并不会执行),这样content的内容最终会渲染到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>
最终结果是这样的。
v-html渲染结果
但是其中的xss代码并没有被执行,或者说那个img元素,压根没被渲染。
XSS攻击失败
控制台中的结果证明了这个结论。
组件的双向绑定
首先这个让我想起了v-model这个指令。
<input v-model="searchText">
它实质上是一个语法糖,包含了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内部,使用v-bind和v-on组合的方式,将组件的value属性绑定到template中输入框的value属性中,并且处理好v-on中的input事件。
于是Vue实例就可以使用v-model来绑定实例中的数据。
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>
最终的结果如图:
点击中间的按钮
点击下方的按钮
网友评论