组件基础
示例
<body>
<div id="app">
<button-counter></button-counter>
<button-counter></button-counter>
</div>
<script type="text/javascript">
Vue.component('button-counter',{
data() {
return {
count:0
}
},
template:'<button v-on:click="count++">You clicked me {{ count }} times.</button>',
})
var vm = new Vue({
el: '#app',
});
</script>
</body>
从这个简单的例子看来
因为组件是可复用的 Vue 实例,所以它们与 new Vue
接收相同的选项,例如 data
、computed
、watch
、methods
以及生命周期钩子等。仅有的例外是像 el
这样根实例特有的选项。
复用
你可以将组件进行任意次数的复用。
注意当点击按钮时,每个组件都会各自独立维护它的 count
。因为你每用一次组件,就会有一个它的新实例被创建。
因此,data必须是一个函数
当我们定义这个 <button-counter>
组件时,你可能会发现它的 data
并不是像这样直接提供一个对象:
data: {
count: 0
}
取而代之的是,一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:
data: function () {
return {
count: 0
}
}
props属性
props属性可以用来向组件传递数据
向刚刚的组件中加入props属性
Vue.component('button-counter',{
props:['title'],
data() {
return {
count:0
}
},
template:'<button v-on:click="count++">{{title}}: You clicked me {{ count }} times.</button>',
})
一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。在上述模板中,你会发现我们能够在组件实例中访问这个值,就像访问 data
中的值一样。
一个 prop 被注册之后,你就可以像这样把数据作为一个自定义 attribute 传递进来:
<button-counter title="button1"></button-counter>
<button-counter title="button2"></button-counter>
看起来当组件变得越来越复杂的时候,例如博文不只需要标题和内容,还需要发布日期、评论等等。为每个相关的信息定义一个 prop 会变得很麻烦:
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
v-bind:content="post.content"
v-bind:publishedAt="post.publishedAt"
v-bind:comments="post.comments"
></blog-post>
所以是时候重构一下这个 <blog-post>
组件了,让它变成接受一个单独的 post
prop:
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
></blog-post>
Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<div v-html="post.content"></div>
</div>
`
})
单个根元素
every component must have a single root element (每个组件必须只有一个根元素)。
模板的内容包裹在一个父元素内
$emit
https://cn.vuejs.org/v2/api/#vm-emit
<body>
<div id="app">
<button-counter title="button1" @click-now="clickNow"></button-counter>
<button-counter title="button2" @click-now="clickNow"></button-counter>
</div>
<script type="text/javascript">
Vue.component('button-counter',{
props:['title'],
data() {
return {
count:0
}
},
template:'<button v-on:click="clickFun">{{title}}: You clicked me {{ count }} times.</button>',
methods: {
clickFun:function(){
this.count++;
this.$emit('click-now',this.title,this.count);
}
},
})
var vm = new Vue({
el: '#app',
methods: {
clickNow:function(title,count){
console.log(title+' has been clicked '+count+' times.');
}
},
});
</script>
</body>
逻辑为:
模板内的v-on:click="clickFun"
绑定到模板method clickFun
在clickFun
内通过$emit函数调用了click-now:
,并且传入两个参数
在外部使用模板时候,通过@click-now="clickNow"
将click-now
与clickNow
绑定,进而调用vm中的方法
插槽
在模板内用<slot></slot>
包裹
<alert-box>
Something bad happened.
</alert-box>
可能会渲染出这样的东西:
幸好,Vue 自定义的 <slot>
元素让这变得非常简单:
Vue.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})
注意事项总结
- 自定义组件需要有一个root element
- 父子组件的data是无法共享
- 组件可以有data,methods,computed....,但是data 必须是一个函数
- new Vue其实就是注册一个root组件
组件通信
父传子
(1) 示例
父传子通过props属性
<body>
<div id="app">
<blog-post title="journey" author="Tony" :likes="likes"></blog-post>
</div>
<script type="text/javascript">
Vue.component('blog-post', {
props: {
title: String,
likes: Number,
isPublished: {
type: Boolean,
default: true,
},
author: {
type: String,
required: true
}
},
template: `
<div>
<p>{{title}} by {{author}} ispublish:{{isPublished}} likes{{likes}}</p>
</div>`,
})
var vm = new Vue({
el: '#app',
data() {
return {
likes:10
}
},
methods: {
},
});
</script>
</body>
</html>
注意,在父组件root里面有个data为likes,如果要简单的传字符串用
title=xxx, author=xxx
就可以了但是如果要传类似数据、布尔值,要用动态绑定传法
:likes="likes"
(2) 属性验证
有时候写组件和用组件的人不一样 可能会出错
比如 我们在用上面组件的时候
<blog-post title="journey" author="Tony" :likes="likes" is-published="false"></blog-post>
这是错误的,但是很难发现
好在,我们在定义组件的时候作了属性验证
isPublished: {
type: Boolean,
default: true,
},
浏览器会报错,因此我们需要修改为
<blog-post title="journey" author="Tony" :likes="likes" :is-published="false"></blog-post>
<!-- 动态绑定is-published -->
假如我们简单的定义属性
props=['isPublished']
,那就不会检测出来
子传父
子传父通过事件向上传
<body>
<div id="app">
<child @myevent="handleEvent"></child>
</div>
<script type="text/javascript">
Vue.component('child', {
template: `
<div>
<p>子组件</p>
<button @click="payMoney">点击</button>
</div>`,
methods:{
payMoney(){
this.$emit("myevent",100)
}
}
})
var vm = new Vue({
el: '#app',
data() {
return {
likes:10
}
},
methods: {
handleEvent(a){
console.log("父组件收到"+a+"元");
}
},
});
</script>
</body>
</html>
-
在子组件的模板里通过@绑定事件给子组件定义的方法
-
在子组件定义的方法中用
this.$emit("funtion",...)
进行事件调用以上述为例
this.$emit("myevent",100)
就是调用myevent方法,传参数100 -
在调用子组件的时候对myevent进行绑定
<child @myevent="handleEvent"></child>
这里是动态绑定父组件的handleEvenet方法
注意:
<child @myevent="handleEvent"></child>
默认传参数
<child @myevent="handleEvent()"></child>
默认参数为空,如果要加括号,可以用
<child @myevent="handleEvent($event)"></child>
来传参数
ref
我们分布给原生组件和自定义组件加上ref属性,并通过父组件的this.$refs
进行调用
<body>
<div id="app">
<input type="text" ref="mytext"/>
<child ref="mychild"></child>
<button @click="handleClick">点击</button>
</div>
<script type="text/javascript">
Vue.component("child",{
template:`
<div>
我是一个用户
</div>`,
data() {
return {
name:"子组件信息"
}
},
})
var vm = new Vue({
el: '#app',
methods: {
handleClick(){
console.log(this.$refs.mytext);
console.log(this.$refs.mychild);
}
},
});
</script>
</body>
点击按钮发现原生组件获取到的是dom节点,自定义组件获取到的是组件的详细信息
所以我们修改一下log的信息为
handleClick(){
console.log(this.$refs.mytext.value);
console.log(this.$refs.mychild.name);
}
发现这是一种降维打击的方法,可以获取到所有的信息!
事件总线/非父子通信
<body>
<div id="app">
<author></author>
<user></user>
</div>
<script type="text/javascript">
var bus = new Vue();
Vue.component("author",{
template:`
<div>
<input type="text" ref="mytext"/>
<button @click="handleClick">发布消息</button>
</div>`,
methods:{
handleClick(){
bus.$emit("message",this.$refs.mytext.value)
}
}
})
Vue.component("user",{
template:`
<div>
我是一个用户
</div>`,
mounted() {
console.log("mounted钩子");
bus.$on("message",(data)=>{
console.log('收到消息',data);
})
},
})
var vm = new Vue({
el: '#app',
data() {
return {
likes:10
}
},
});
</script>
</body>
两个组件之间的通信可以通过事件总线进行
事件总线就是上述代码中的var bus = new Vue();
发送方通过.$emit
函数进行调用
接收方通过.$on
可以进行监听
bus.$emit(标识符,参数...)
bus.$on(标识符,函数)
动态组件
<component>
元素,动态地绑定多个组件到它的 is 属性<keep-alive>
保留状态,避免重新渲
<body>
<div id="app">
<component :is="who"></component>
<footer>
<ul>
<li><a @click="who='home'">首页</a></li>
<li><a @click="who='list'">列表</a></li>
<li><a @click="who='shopcar'">购物车</a></li>
</ul>
</footer>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {
who: 'home'
},
components: {
"home": {
template: `<div>home<input type="text"/></div>`
},
"list": {
template: `<div>list</div>`
},
"shopcar": {
template: `<div>shopcar</div>`
}
}
});
</script>
</body>
但是,还有一点问题,在home界面的输入框输入后,切换页面回来发现没了,怎么保留?
只要用keep-alive
包裹就可以
<keep-alive>
<component :is="who"></component>
</keep-alive>