一、组件基础
1.1 组件的使用
组件是可复用的 Vue 实例,且带有一个名字。
因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
一个组件的data选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。
组件的template有三种写法:
<div id="app">
<article-vue></article-vue>
<article-vue2></article-vue2>
<article-vue3></article-vue3>
</div>
<script type="text/x-template" id="temp2">
<div>
<h2>标题2</h2><p>这里是正文2...{{message}}</p>
</div>
</script>
<template id="temp3">
<div>
<h2>标题3</h2><p>这里是正文3...</p>
</div>
</template>
<script type="text/javascript">
Vue.component('article-vue', {
template: '<div><h2>标题</h2><p>这里是正文...</p></div>'
});
Vue.component('article-vue2', {
template: '#temp2',
data: function(){
return {
message: 'lazyrabbit'
}
}
});
var app = new Vue({
el: '#app',
data: {
message: 'lazyrabbit'
},
components: {
'article-vue3': {// vue不可以使用横线 ,所以使用引号引起来
template: '#temp3'
}
}
});
</script>
字符串模板:指的是在组件选项里用 template:”” 指定的模板,换句话说,写在 js 中的 template:”” 中的就是字符串模板
非字符串模板:在单文件里用 指定的模板,换句话说,写在 html 中的就是非字符串模板。
每个组件必须只有一个根元素。
1.2 组件名
Vue.component('my-component-name', { /* ... */ })
定义组件名的方式有两种:
1、使用 kebab-case (短横线分隔命名)
2、使用 PascalCase (首字母大写命名)
当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说
强烈推荐遵循 W3C 规范中的自定义组件名 (字母全小写且必须包含一个连字符)
1.3 全局注册
使用Vue.component注册的组件都是全局组件
1.4 局部注册
在需要注册的组件的components属性中定义
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
局部注册的组件在其子组件中不可用,若要使用则需要在子组件内部再使用components定义组件
二、通过 Prop 向子组件传递数据
2.1 Prop
Prop 是你可以在组件上注册的一些自定义 attribute。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个属性。
HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名),如果使用的是字符串模板,那么这个限制就不存在了
Vue.component('blog-post', {
// 在 JavaScript 中是 camelCase 的
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})
<blog-post post-title="hello!"></blog-post>
prop 支持多种类型:
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor
}
2.2 传递静态或动态 Prop
直接在内部直接定义属性来传递静态值,也可以通过 v-bind 动态赋值。
<!-- 静态的值 -->
<blog-post title="My journey with Vue"></blog-post>
<!-- 动态赋予一个变量的值 -->
<blog-post v-bind:title="post.title"></blog-post>
<!-- 动态赋予一个复杂表达式的值 -->
<blog-post
v-bind:title="post.title + ' by ' + post.author.name"
></blog-post>
2.3 传入一个对象
如果你想要将一个对象的所有属性都作为 prop 传入,你可以使用不带参数的 v-bind (取代 v-bind:prop-name)
post: {
id: 1,
title: 'My Journey with Vue'
}
<blog-post v-bind="post"></blog-post>
<blog-post
v-bind:id="post.id"
v-bind:title="post.title"
></blog-post>
三、监听子组件事件
3.1 $emit
子组件可以通过调用 $emit 方法并传入事件名称来触发一个父组件事件,父组件的名称为传入的第一个参数。
<div id="app">
<article-vue v-bind='article' @more="more"></article-vue>
<p>已被查看{{clickTimes}}次</p>
</div>
<template id="temp">
<div>
<h2>{{title}}</h2>
<p>{{content}}<a href="javascript:void(0);" @click="getMore">...</a></p>
</div>
</template>
<script type="text/javascript">
Vue.component('article-vue',{
template: '#temp',
props: ['title','content'],
methods: {
getMore: function(){
this.$emit('more');
}
}
});
var article = {
title: '阿森纳主帅阿尔特塔确诊新冠状病毒',
content: '阿森纳球员将自我隔离14天'
}
var app = new Vue({
el: '#app',
data: {
article: article,
clickTimes: 0
},
methods: {
more: function(){
this.clickTimes++;
}
}
});
</script>
若想向父组件传入一个值,可以在 $emit 方法上传入第二个参数,当在父级组件监听这个事件的时候,这个值将会作为第一个参数传入这个方法。
3.2 事件名
不同于组件和 prop,事件名不会被用作一个 JavaScript 变量名或属性名,所以就没有理由使用 camelCase 或 PascalCase 了。并且 v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的)。
官网推荐始终使用 kebab-case 的事件名。
3.3 在组件上使用 v-model
在html元素中使用v-model时 v:model实际是使用了bind和click事件来做到双向绑定
<input v-model="searchText">
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
在组件上时,v-model的实际效果:
<custom-input v-model="searchText"></custom-input>
<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"
></custom-input>
因此,在组件上使用v-model指令时,需要在组件内部做以下两件事:
- 绑定属性
- input 事件被触发时,将新的值通过自定义的 input 事件抛出
Vue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`
})
一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的。model 选项可以用来避免这样的冲突:
<base-checkbox v-model="lovingVue"></base-checkbox>
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
3.4 .sync 修饰符
在有些情况下,我们可能需要对一个 prop 进行“双向绑定”。不幸的是,真正的双向绑定会带来维护上的问题,因为子组件可以修改父组件,且在父组件和子组件都没有明显的改动来源。
子组件触发父组件的update:title事件并传递一个新的title值
this.$emit('update:title', newTitle)
在父组件中更新title
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>
上边的写法等同于
<text-document v-bind:title.sync="doc.title"></text-document>
四、插槽
4.1
在模板内部可以使用
<div id="app">
<article-vue v-bind='article' @more="more"><p>已被查看{{clickTimes}}次</p></article-vue>
</div>
<template id="temp">
<div>
<h2>{{title}}</h2>
<p>{{content}}<a href="javascript:void(0);" @click="getMore">...</a></p>
<slot></slot>
</div>
</template>
4.2 编译作用域
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
4.3 默认内容
在slot元素内可以添加默认内容,使用组件时没有提供任何插槽内容时,默认内容就会生效。
<slot>已被查看0次</slot>
4.4 具名插槽
当组件内部有多个插槽时,可以指定slot的name属性。
一个不带 name 的
在向具名插槽提供内容的时候,我们可以在一个 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称:
<div id="app">
<article-vue v-bind='article' @more="more">
<p>已被查看{{clickTimes}}次</p>
<template v-slot:like>
<span> 10 个人点了攒</span>
</template>
</article-vue>
</div>
<template id="temp">
<div>
<h2>{{title}}</h2>
<p>{{content}}<a href="javascript:void(0);" @click="getMore">...</a></p>
<slot></slot>
<slot name="like"></slot>
</div>
</template>
v-slot 只能添加在 上
在 2.6.0 中,具名插槽和作用域插槽引入了一个新的统一的语法 (即 v-slot 指令)。它取代了 slot 和 slot-scope 这两个目前已被废弃但未被移除且仍在文档中的 attribute。
4.5 在插槽中使用组件内的数据
在slot元素内绑定属性,在父作用域的template中就可以在v-slot指令种定义插槽种提供数据的名字,然后就可以直接使用了。
<div id="app">
<article-vue v-bind='article' @more="more">
<p>已被查看{{clickTimes}}次</p>
<template v-slot:like="slotProps">
<span> 10 个人为{{slotProps.title}}点了攒</span>
</template>
</article-vue>
</div>
<template id="temp">
<div>
<h2>{{title}}</h2>
<p>{{content}}<a href="javascript:void(0);" @click="getMore">...</a></p>
<slot></slot>
<slot name="like" v-bind:title="title"></slot>
</div>
</template>
在上述情况下,当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。这样我们就可以把 v-slot 直接用在组件上:
<article-vue v-slot="slotProps">
<span> 10 个人为{{slotProps.title}}点了攒</span>
</article-vue>
4.6 2.6.0 新增
动态指令参数也可以用在 v-slot 上,来定义动态的插槽名:
<template v-slot:[dynamicSlotName]>
...
</template>
跟 v-on 和 v-bind 一样,v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #