组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data
、computed
、watch
、methods
以及生命周期钩子等。仅有的例外是像 el
这样根实例特有的选项。
定义组件的名称写法
使用 kebab-case
Vue.component('my-component', { /* ... */ })
使用自定义组件 <my-component>
使用 PascalCase
Vue.component('MyComponent', { /* ... */ })
使用自定义组件 <MyComponent>
全局注册
这种方式是全局注册组件
Vue.component('MyComponent', { /* ... */ })
new Vue({ el: '#app' })
局部注册
这是局部注册组件,局部注册组件的好处主要是避免一次性加载多个组件,只加载所需的组件
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
es6写法
import ComponentA from './ComponentA.vue'
export default {
components: {
ComponentA
},
// ...
}
组件data必须是一个函数
每个组件的实例生成的data独立拷贝,避免操作某一个组件里的data里的属性影响其他相同的组件值
props
在子组件里添加props选项,props是一个数组,里面的每一项都是接受父组件传递过来的值
const buttonComponent = {
name: 'buttonComponent',
props: ['postTitle'],
data() {
return {
count: 0
}
},
template: '<div v-bind:title="postTitle" v-on:click="count++">点击我变化{{count}}</div>'
};
父组件如果在teplate里调用的话,属性名称可以通过驼峰式大小写命名来传递
var app = new Vue({
el: '#app',
name: 'app',
components: {
'button-counte': buttonComponent
},
template:'<button-counte postTitle="123123"> </button-counte>'
});
如果组件是在html代码里去调用只能使用kebab-case
方式,因为HTML对大小写不敏感
var app = new Vue({
el: '#app',
name: 'app',
components: {
'button-counte': buttonComponent
}
});
<body>
<div id="app">
<button-counte post-title="11112"> </button-counte>
</div>
</body>
prop值类型
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor
}
prop传递静态和动态值
静态之传入的写法
<button-counte postTitle="123123"> </button-counte>
动态之传入的写法,通过v-bind:
去绑定动态值
const value ='1111';
<button-counte v-bind:postTitle="value"> </button-counte>
传递数字(number),依然需要用到v-bind:
<button-counte v-bind:postTitle="42"> </button-counte>
传入一个布尔值
<!-- 当postTitle值等于 false -->
<button-counte v-bind:postTitle="false"> </button-counte>
<!-- 当postTitle值等于 true -->
<button-counte postTitle> </button-counte>
传入一个数组和传入一个对象
<!--尽管数组和对象都是静态传入,但是都是都需要v-bind 静态数组-->
<button-counte v-bind:comment-ids="[234, 266, 273]"></button-counte>
<!--静态对象-->
<button-counte
v-bind:author="{
name: 'Veronica',
company: 'Veridian Dynamics'
}"
></button-counte >
传入一个对象的所有 property
传入整个对象属性,v-bind
(取代 v-bind:prop-name
)。例如,对于一个给定的对象 post
post: {
id: 1,
title: 'My Journey with Vue'
}
<blog-post v-bind="post"></blog-post>
单向数据流
prop向子组件传值是一个单向从上到下的一个方式,反过来却不行,直接在子组件里改变prop传过来的值浏览器将会报错。
我们可以通过data的属性去接受
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
prop类型检查和验证
prop type类型可以是以下类型
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
除此之外,还可以自定义类型,需要自定义构造函数,并且通过
instanceof
来检查确认
function Person (firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
Vue.component('blog-post', {
props: {
author: Person
}
})
验证author
属性是否由Person
创建
如果没有定义prop接受传递的参数,默认会把prop传过来的属性添加到组件的dom属性中,例:
我们给button-counte
绑定abc
传给prop
<button-counte postTitle="123123" v-bind:abc="message"> </button-counte>
但是我们在子组件中并没有定义prop
去接受
const buttonComponent = {
name: 'buttonComponent',
props: ['postTitle'],
data() {
return {
count: 0
}
},
template: '<div v-bind:title="postTitle" v-on:click="count++">点击我变化{{count}}</div>'
};
浏览器dom结构如下
<div title="123123" abc="hello">点击我变化0</div>
class 和 style自动合并
如果在父组件传入class
和style
<button-counte class="f" style="font-size:16px;"> </button-counte>
子组件有默认的class
和style
const buttonComponent = {
name: 'buttonComponent',
template: '<div class="c" style="color:red">点击我变</div>'
}
浏览器dom结构如下
<div class="c f" style="color: red; font-size: 16px;">点击我变</div>
除此之外,父组件向子组件传递其他属性,将会优先于子组件定义的属性,父组件将会覆盖替换子组件的属性
属性继承
如果我们给子组件加入value
属性,默认子组件会继承整个属性
<button-counte value="123" class="f" style="font-size:16px;"> </button-counte>
浏览器dom
<div class="c f" value="123" style="color: red; font-size: 16px;">点击我变</div>
如果我们要取消属性继承,可以在子组件里添加inheritAttrs: false
选项
const buttonComponent = {
name: 'buttonComponent',
inheritAttrs: false,
template: '<div class="c" style="color:red">点击我变</div>'
};
var app = new Vue({
el: '#app',
name: 'app',
components: {
'button-counte': buttonComponent
},
data: {
message: 'hello'
},
template:'<button-counte value="123" class="f" style="font-size:16px;"> </button-counte>'
});
浏览器dom将不会再继承value
这个属性
<div class="c f" style="color: red; font-size: 16px;">点击我变</div>
当然这个对class
和style
不起作用
自定义事件
父组件监听子组件的事件
父组件通过在调用子组件时,在组件属性上添加v-on
来监听子组件的事件
<button-counte v-bind:message="message" v-on:hand="change"> </button-counte>
子组件通过$emit
方法来触发父组件的hand
事件,从而调用父组件的change
方法:
完整代码
const buttonComponent = {
name: 'buttonComponent',
template: `<div v-on:click="$emit('hand')">{{message}}</div>`,
props:['message']
};
var app = new Vue({
el: '#app',
name: 'app',
components: {
'button-counte': buttonComponent
},
data: {
message: 'hello'
},
methods:{
change(){
this.message = this.message+ 'hellow';
}
},
template:'<button-counte v-bind:message="message" v-on:hand="change"> </button-counte>'
});
$emit
第一个参数传事件名称,第二个参数可以传递参数,在父组件里的事件下的方法可以调用第二个参数
//子组件
<div v-on:click="$emit('hand','123')">{{message}}</div>
//父组件
methods:{
change(e){
this.message = this.message+ 'hellow';
console.log(e) //输出'123'
}
},
template:'<button-counte v-bind:message="message" v-on:hand="change"> </button-counte>'
面试中常问到的v-model
v-model
实际上是一种语法糖,真正实现的原理如下:
<!--v0-model-->
<input v-model="data">
input v-model
等价于下面代码,实际上为我们做了两步,一个是绑定input的value
值,一个是监听input值的变化
<input
v-bind:value="data"
v-on:input="data = $event.target.value"
>
这是v-model
实现的双向绑定的原理
slot
插槽
基本用法
子组件定义插槽位置
const buttonComponent = {
name: 'buttonComponent',
template: `<div><slot></slot></div>`,
};
父组件调用组件,在组件中两个标签中间添加对应的内容,将会传递给子组件插槽的位置
var app = new Vue({
el: '#app',
name: 'app',
components: {
'button-counte': buttonComponent
},
template:'<button-counte>这是一个插槽 </button-counte>'
});
浏览器dom
<div>这是一个插槽 </div>
当然父组件还可以传入html代码
<button-counte><span style="color:red">这是一个插槽 </span></button-counte>
浏览器dom
<div><span style="color: red;">这是一个插槽 </span></div>
这就是slot
的魅力
slot默认值和默认值替换
如果在子组件里<slot>abc</slot>
标签中添加其他内容(例如:abc
),可以理解为是插槽的默认值,父组件如果直接调用就会显示abc
;
<button-counte></button-counte>
<!--以上会显示`abc`-->
如果父组件调用改组件时候往插槽里添加内容,那么子组件里的abc
将会被父组件传来的内容替换
<button-counte>hello word</button-counte>
<!--以上会显示`hello word`-->
slot具名插槽
具名插槽就是给slot定义名字,通过在<slot></slot>
中添加 name="header"
属性,在父组件中,通过<template v-slot:header>
来调用,下面我们以网页布局为一个例子:
const buttonComponent = {
name: 'buttonComponent',
template:
`<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>`,
};
var app = new Vue({
el: '#app',
name: 'app',
components: {
'button-counte': buttonComponent
},
template:
`<button-counte>
<template v-slot:header>
<h1>这是header</h1>
</template>
<template v-slot:footer>
<h1>这是footer</h1>
</template>
<div>这是中间内容</div>
</button-counte>`
});
打开浏览器一看dom如下
<div class="container">
<header><h1>这是header</h1></header>
<main> <div>这是中间内容</div></main>
<footer><h1>这是footer</h1></footer>
</div>
我们发现header
和footer
插槽能够找到自己的位置,而中间的没有添加名字的也能将默认值添加进去,具名插槽不受顺序影响
作用域插槽
父组件如果要访问子组件的变量值可以通过插槽方式去使用,子组件首先需要通过v-bind
将user
数据绑定在slot
上,父组件通过v-slot
加具名来获取变量,完整代码如下
const buttonComponent = {
name: 'buttonComponent',
data(){
return{
user: {
first: 'li',
last: 'jie'
}
}
},
template:
`<div class="container">
<slot v-bind:user="user">{{user.last}}</slot>
</div>`,
};
var app = new Vue({
el: '#app',
name: 'app',
components: {
'button-counte': buttonComponent
},
template:
`<button-counte>
<template v-slot:default="slotProps">
{{ slotProps.user.first }}{{slotProps.user.last}}
</template>
</button-counte>`
});
浏览器dom
<div class="container">
lijie
</div>
但是,我们不能在<template>
使用{{slotProps.user.last}}
,否则会报错
<div>
<button-counte>
<template v-slot:default="slotProps">
{{ slotProps.user.first }}{{slotProps.user.last}}
</template>
</button-counte>
{{slotProps.user.last}}
</div>
具名插槽缩写
例如 v-slot:header 可以被重写为 #header
<button-counte>
<template #heade>
....
</template>
</button-counte>
动态组件和异步组件
keep-alive
可以缓存动态组件
<!--抄的官网例子-->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>