项目前导 学习笔记
一、自定义组件
有时候有一组 html 结构的代码,且其可能还绑定了事件。然后这段代码可能有多个地方都被使用到了,如果都是拷贝来拷贝去,很多代码都是重复的,包括事件部分的代码都是重复的。那么这时候我们就可以把这些代码封装成一个组件,以后在使用的时候就和使用普通的 html 标签
一样,直接拿过来用就可以了(且不相互影响)。
注意:使用 Vue.component
创建
1.1、基本使用
<div id="app">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
<script>
Vue.component(
// 自定义组件( button-counter 为自定义的标签名 )
'button-counter', {
template: '<button @click="co unt+=1">点击了{{count}}次</button>',
data: function(){
return {
count: 0
}
}
}
);
let vm = new Vue({
el: "#app",
data: {}
});
</script>
以上我们创建了一个叫做 button-counter
的组件,这个组件实现了能够记录点击了多少次按钮的功能。后期如果我们想要使用,就直接通过 button-counter 使用就可以了。然后因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods
以及生命周期钩子等。仅有的例外是像 el
这样根实例特有的选项。另外需要注意的是:组件中的 data 必须为一个函数!
1.2、给组件添加属性
像原始的 html 元素都有自己的一些属性,而我们自己创建的组件,也可以通过 prop
来添加自己的属性。这样别人在使用你创建的组件的时候就可以传递不同的参数了。
<div id="app">
<!-- 通过自定义组件添加属性实现 -->
<blog-post v-bind:lis="blogs"></blog-post>
</div>
<script>
Vue.component(
'blog-post', {
props: ['lis'],
// 包含多行要用 `` 括起来
template:
`
<table>
<tr>
<th>序号</th>
<th>标题</th>
</tr>
// 这里循环的应是组件定义的属性, 该属性相当于是参数 传入对象 blogs
<tr v-for="(blog,index) in lis">
<td>{{index+1}}</td>
<td>{{blog.title}}</td>
</tr>
</table>
`
}
)
new Vue({
el: "#app",
data: {
blogs: [
{"title":"钢铁是怎样练成的?","id":1},
{"title":"AI会毁灭人类吗?","id":2},
{"title":"如何学好Vue!","id":3},
]
}
});
</script>
1.3、单一根元素
如果自定义的组件中,会出现很多 html 元素,那么根元素必须只能有一个(最外层只有一个标签,其余标签都在该标签内),其余的元素必须包含在这个根元素中。比如以下是一个组件中的代码,会报错:
<h3>{{ title }}</h3>
<div v-html="content"></div>
应该改成:
<div>
<h3>{{ title }}</h3>
<div v-html="content"></div>
</div>
1.4、事件传递
子组件(自己定义的组件)中添加事件跟之前的方式是一样的,然后如果发生某个事件后想要通知父组件(new Vue),那么可以使用 this.$emit
函数来实现。
- 例:点击选择,显示已选项,再次点击取消选择
<div id="app">
<blog-item v-for="i in blogs" v-bind:blog="i" @check-changed="checks"></blog-item>
<div v-for="n in componentblog">
{{n.title}}
</div>
</div>
<script>
Vue.component('blog-item',{
props:['blog'],
// 同样的, 这里的 blog 都是组件的属性
template:`
<div>
<span>{{blog.title}}</span>
<input type="checkbox" @click="onCheck">
</div>
`,
methods:{
onCheck:function(){
// console.log(123)
// 'check-changed' 即为组件触发事件的属性
// 可以理解为子组件(自定义组件)通知父组件(上面HTML代码,即new Vue)执行 check-changed 的事件(看上面代码)
this.$emit('check-changed',this.blog)
}
}
})
new Vue({
el: '#app',
data: {
blogs:[
{"title":"钢铁是怎样练成的?","id":1},
{"title":"AI会毁灭人类吗?","id":2},
{"title":"如何学好Vue!","id":3},
],
componentblog:[]
},
methods:{
checks:function(blog){
// indexOf 判断某个元素在数组中的位置, 返回下标(下标从0开始)
var index = this.componentblog.indexOf(blog)
// 若已存在则删除
if(index >= 0){
this.componentblog.splice(index,1)
}
// 不存在则添加
else{
this.componentblog.push(blog)
}
// 显示已选项
console.log(blog)
}
}
})
</script>
需要注意的是,因为 html 中大小写是不敏感的,所以在定义子组件传给父组件事件名称的时候,不要使用 myEvent
这种驼峰命名法,而是使用 my-event
这种规则。
1.5、自定义组件 v-model
一个组件上的 v-model
默认会利用名为 value 的 prop(属性) 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value 特性用于不同的目的。这时候我们可以在定义组件的时候,通过设置 model 选项可以用来实现不同的处理方式。
<div id="app">
<stepper v-model:value="goods_count"></stepper>
</div>
<script>
Vue.component('stepper',{
props:['count'],
model:{
// 什么情况下触发这个 v-model 的行为
event: 'count-changed',
// 处理的对象
prop: "count"
},
template:`
<div>
<button @click="sub">-</button>
<span>{{count}}</span>
<button @click="add">+</button>
</div>
`,
methods:{
sub:function(){
// 也可以判断一下 if (this.count > 0)
// 触发顺序 点击 → sub() → <stepper> → v-model → count → goods_count
this.$emit("count-changed", this.count-1)
},
add:function(){
this.$emit("count-changed", this.count+1)
}
}
});
new Vue({
el: "#app",
data:{
"goods_count":0
}
})
</script>
其中的 props
定义的属性分别是给外面调用组件的时候使用的。model 中定义的 prop:'count'
是告诉后面使用 v-model 的时候,要修改哪个属性;event:'count-changed'
是告诉 v-model ,后面触发哪个事件的时候要修改属性。
1.6、插槽
我们定义完一个组件后,可能在使用的时候还需要往这个组件中插入新的元素或者文本(但是不修改组件的模板),这时候就可以使用插槽来实现。
<div id="app">
<navigation-link :url="url">
<!-- 在这添加的位置就是 slot 留出来的插槽 -->
个人中心
</navigation-link>
</div>
<script>
Vue.component('navigation-link', {
props: ['url'],
template: `
<a v-bind:href="url" class="nav-link">
<!-- 插槽的优先级高于模板的优先级 -->
<slot>关于</slot>
</a>
`
})
new Vue({
el: "#app",
data: {
'url': "https://www.baidu.com/"
}
});
</script>
如果要添加多个插槽,那么就要为每个插槽命名,否则所有插槽都会显示相同的内容。
<navigation-link url="/profile">
个人中心
</navigation-link>
...
<slot>关于</slot>
<slot>关于</slot>
<slot>关于</slot>
...
起个名字,用 v-slot:
引用名字,没有的话就是默认的名字 default
<navigation-link :url="url">
<!-- 在这添加的位置就是 slot 留出来的插槽 -->
<!-- 个人中心 -->
<!-- 要用 template 标签 -->
<template v-slot:one>第一个</template>
<template>第二个</template>
<template v-slot:three>第三个</template>
</navigation-link>
...
<a v-bind:href="url" class="nav-link">
<!-- 插槽的优先级高于模板的优先级 -->
<!-- 为了好看点 可以加个 div -->
<div> <slot name="one"></slot> </div>
<div> <slot name="default"></slot> </div>
<div> <slot name="three"></slot> </div>
</a>
...
1.7、插槽的作用域
<div id="app">
<container>
<!-- 其中 h 包含模板中调用的所有数据对象 -->
<template v-slot:header="h">
<!-- 使用 h.属性名 来取得调用的值 -->
头部区域:{{h.hva}} -> {{h.hf}}
</template>
<template v-slot:footer="f">
尾部区域:{{f.fva}}
</template>
</container>
</div>
<script>
Vue.component(
'container', {
template:
`
<div>
<!-- 使用属性调用定义的数据对象 -->
<div><slot name="header" :hva="navs" :hf="addr"></slot></div>
<div><slot name="footer" :fva="addr"></slot></div>
</div>
`,
data(){
return {
navs: ['新闻', 'hao123', '地图'],
addr: "百度"
}
}
}
);
new Vue({
el: "#app"
});
</script>