组件基础
❝组件是可复用的 Vue 实例
❞
注册方法
组件名
- 短横线分隔命名。 比如
<my-component> - 首字母大写命名。 比如
<MyComponent>
全局注册
<div id="app">
<my-component></my-component>
</div>
<script>
Vue.component('my-component',{
template: '<div>我是组件的内容</div>'
})
var app = new Vue({
el: '#app'
})
</script>
- 优点: 所有 vue 实例都可以用
- 缺点: 权限太大,容错率降低
局部注册
- 局部注册的组件在其子组件中不可用
<div id="app">
<my-component></my-component>
</div>
<script>
var app = new Vue({
el: '#app',
components: {
'my-component':{
template: '<div>我是组件的内容</div>'
}
}
})
</script>
问题
Vue 组件模板会受到 html 限制,比如<table>中只能有<tr><td>
「解决:只能用 is 挂载属性」
<table id='app'>
<td is="my-component"></td>
</table>
<script>
var app = new Vue({
el: '#app',
components: {
'my-component': {
template: '<div>hello world</div>'
}
}
})
</script>
注意
- 组件名必须是小写且包含连字符-
- template 必须被 DOM 元素包裹
- 组件的定义中,除了 template 之外还有 data,computed,methods 等
- 「
一个组件的data必须是一个函数」,组件不会互相影响 实例- 组件是可复用的,组件内的 data 必须互不影响,若 data 以对象多的形式存在,js 对象是引用类型,若对某个组件的 data 修改,会影响到其它组件的 data,因此 data 必须以函数的形式返回
- 为了实现每个组件可以维护独立的数据拷贝,互不影响
使用 props 传递数据(父-> 子传递数据)
当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property
<div id="app" style='border: 2px solid red'>
<h5>我是父组件</h5>
<child-component msg='我是来自父组件的内容'></child-component>
</div>
<!--这里都是父组件作用域-->
<script>
var app = new Vue({
el: '#app',
components: {
'child-component': {
props: ['msg'],
//template是子组件作用域
template: '<div style="border: 2px solid green">{{msg}}</div>'
}
}
})
</script>
vue 的父组件和子组件生命周期钩子执行顺序
父组件挂载完成必须等到子组件挂载完成之后
[父]beforeCreate -> [父]created -> [父]beforeMount -> [子]beforeCreate -> [子]created -> [子]beforeMount -> [子]mounted -> [父]mounted
- 父组件更新过程
- 影响到子组件:[父]beforeUpdate -> [子]beforeUpdate -> [子]updated -> [父]updated
- 不影响到子组件:[父]beforeUpdate -> [父]updated
- 子组件更新过程
- 影响到父组件:[父]beforeUpdate -> [子]beforeUpdate -> [子]updated -> [父]updated
- 不影响父组件:[子]beforeUpdate -> [子]updated
v-bind 动态传递 prop
<div id="app" style='border: 2px solid red'>
<input type="text" v-model='parentmsg'>
<bind-component :msg='parentmsg'></bind-component>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
parentmsg: 'hello world'
//绑定的属性值改变,传递的值也变化,动态
},
components: {
'bind-component': {
props: ['msg'],
template: '<div style="border: 2px solid green">{{msg}}</div>'
}
}
})
</script>
单向数据流
❝通过 props 传递数据是单向的,只能父传子
❞
- 目的:尽可能将父子组解稿,避免子组件无意中修改了父组件的状态
- 父组件下可以有多个子组件,若一个子组件能够修改父组件的状态,会使数据流难以理解
- 「这个 prop 用来传递一个初始值」
- 注册组件
- 父组件传递初始值进来,在子组件中用 props 接受
- 最好定义一个本地的 data property 并将这个 prop 用作其初始值
<div id="app">
<my-component msg='我是父组件传递的数据'></my-component>
</div>
<script>
Vue.component('my-component',{
props: ['msg'],
template: '<div>{{count}}</div>',
data:function(){
return {
count: this.msg
}
}
})
var app = new Vue({
el: '#app',
})
</script>
- 「这个 prop 以一种原始的值传入且需要进行切换」
- 注册组件
- 将父组件的数据传递起来,并在子组件中用 prop 接收
- 最好使用这个 prop 的值来定义一个计算属性
<div id="app">
<input type="text" v-model='width' >
<width-component :width='width'></width-component>
</div>
<script>
Vue.component('width-component',{
props: ['width'],
template: '<div :style="style"></div>',
computed: {
style: function(){
return{
width: this.width+'px',
background: 'red',
height: '30px'
}
}
}
})
var app = new Vue({
el: '#app',
data: {
width: 0
}
})
</script>
Prop 数据验证
- 验证的 type 类型可以是
- String
- Number
- Boolean
- Object
- Array
- Function
<div id="app">
<my-component :a='a' :b='b' :c='c' :d='d' :e='e' :f='f'></my-component>
</div>
<script>
Vue.component('my-component',{
props: {
a: Number,
b: [String,Number],
c: {
type: Boolean,
default: true
},
d: {
type: Number,
required: true
},
e: {
type: Array,
defalut: function(){
return []
}
},
f: {
validator: function(value){
return value > 10
}
}
},
template: '<div>{{a}}--{{b}}--{{c}}--{{d}}--{{e}}--{{f}}</div>'
})
var app =new Vue({
el: '#app',
data:{
a: 1,
b: 'abc',
c: true,
d: 1234,
e: ['abc','ddd'],
f: 88
}
})
</script>
组件通信
- 父子组件通信
- 父->子
- 子组件 props 接收数据
- 子->父
- $emit 方法传递参数
- 父->子
- 非父子组件通信
- eventBus,创建一个事件中心用于接收和传递事件
自定义事件 (子组件->父组件传递数据)
- 自定义事件
- v-on 和$emit 组合使用
- 父组件用 v-on 监听事件
- 子组件用$emit 触发事件(传给监听器回调)
- 在自定义事件中用一个参数来接受
<div id="app">
{{fathermsg}}
<my-component @change='sendFather'></my-component>
<!--@change就是自定义事件-->
</div>
<script>
Vue.component('my-component',{
props: ['fathermsg'],
template: "<button @click='childmsg'>点击传递子组件数据</button>",
data(){
return {
msg: "我是来自子组件的数据"
}
},
methods: {
childmsg(){
this.$emit('change',this.msg)
},
}
})
var app = new Vue({
el: '#app',
data: {
fathermsg: '我是来自父组件的数据'
},
methods: {
sendFather: function(value){
this.fathermsg =value
}
}
})
</script>
vm.$emit
- {string} eventName 当前实例上的事件
- [...args] 要传递的参数
在组件中学习 v-model
- v-model 其实是一个语法糖
- v-bind 绑定一个 value 属性
- v-on 指令给当前元素绑定 input 事件
<input v-model='search'>
//等价于
<input v-bind:value='search' v-on:input='search = $event.target.value'>
- 使用 v-model
- 接收一个 value 属性
- 在有新的 value 时触发 input 事件
<div v-model='search'></div>
//等同于
<div v-bind:value='search' v-on:input='search'></div>
<div id="app">
{{fathermsg}}
<my-component v-model="fathermsg"></my-component>
</div>
<script>
Vue.component('my-component',{
template: '<button @click="handle">点击传递数据</button>',
data(){
return {
msg: '我是子组件传递的数据'
}
},
methods: {
handle(){
this.$emit('input',this.msg)
}
}
})
var app = new Vue({
el: '#app',
data: {
fathermsg: '我是父组件数据'
},
})
</script>
非父组件的通信
创建一个事件中心用于接收和传递事件
var bus = new Vue()
<!--触发组件A中的事件-->
bus.$emit('ddaa','传递的数据')
<!--在组件B创建的钩子中监听事件-->
bus.$on('ddaa',function(){
})
<div id="app">
<my-acomponent></my-acomponent>
<my-bcomponent></my-bcomponent>
</div>
<script>
Vue.component('my-acomponent',{
template: '<button @click="handle">点击我向b组件传递数据</button>',
data: function(){
return{
aaa: '我是来自A组件的内容'
}
},
methods: {
handle: function(){
this.$root.bus.$emit('lala',this.aaa)
}
}
})
Vue.component('my-bcomponent',{
template: '<div></div>',
created: function(){
this.$root.bus.$on('lala',function(value){
alert(value)
})
}
})
var app = new Vue({
el: '#app',
data: {
bus: new Vue()
}
})
</script>
vm.$root
实例property,当前组件的根vue实例
vm.$on
监听当前实例上的自定义事件,由$emit触发,回调函数接收所有传入事件触发函数的额外参数
- string event
- {Function} callback
修改父组件属性
<div id='app'>
<child-component></child-component>---{{msg}}
</div>
<script>
Vue.component('child-component',{
template: '<button @click="setFatherData">通过点击我修改父组件的数据</button>',
methods: {
setFatherData: function(){
this.$parent.msg= '数据已经修改了'
}
}
})
var app = new Vue({
el: '#app',
data: {
msg:'数据还未修改'
}
})
<script>
获取子组件属性
通过$refs 获取这个子组件属性
<div id="app">
<my-acomponent ref='A'></my-acomponent>
<my-bcomponent ref='B'></my-bcomponent>
<hr>
<child-component ref='C'></child-component>---{{msg}}
<hr>
<button @click='getChildData'>我是父组件,我要拿到子组件的内容</button>
-----------------{{fromChild}}
</div>
<script>
Vue.component('my-acomponent',{
template: '<div></div>',
data: function(){
return{
msg: '我是A中的msg'
}
}
})
Vue.component('my-bcomponent',{
template: '<div></div>',
data: function(){
return {
msg: '我是来自B中的msg'
}
}
})
Vue.component('child-component',{
template: '<div></div>',
data: function(){
return {
msg: '我是C中的msg'
}
}
})
var app = new Vue({
el: '#app',
data: {
formChild: '还未拿到'
},
methods: {
getChildData: function(){
this.fromChild = this.$refs.A.msg;
}
}
})
</script>
vm.$refs
ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。
插槽
❝组合组件,需要一种方式来混合父组件的内容与子组件自己的模板。这个过程被称为内容分发,Vue,js 实现了一个内容分发的 API,使用特殊的 slot 元素作为原始内容的插槽。
❞
编辑作用域
- 父级模板里的所有内容都是在父级作用域中编译的
- 子模版里的所有内容都是在子作用域中编译的
插槽的用法
混合父组件的内容和子组件自己的模板
- 单个和具名插槽是父组件向子组件传递数据
单个插槽
<slot></slot>
<div id="app">
<my-component>
<p>我是父组件插入的内容</p>
</my-component>
</div>
<script>
Vue.component('my-component',{
template: '<div>\
<slot>\
如果没有插入内容,我就出现\
</slot>\
</div>',
data: function(){
return {
message: '我是子组件的内容'
}
}
})
var app = new Vue({
el: '#app'
})
</script>
在子组件中使用 slot 做一个插槽,父组件中的内容就可以插到这个插槽里边。
具名插槽
- 只能在
<template></template>上使用v-slot指令 - 父组件插入对应名字的子组件 slot 中
- 具名插槽缩写#
<div id="app">
<my-component>
<template v-slot:header>
<h1>Hello</h1>
</template>
<p>vue</p>
<template v-slot:footer>
<p>world</p>
</template>
</my-component>
</div>
<script>
Vue.component('my-component',{
template: "<div>\
<header>\
<slot name='header'></slot>\
</header>\
<main>\
<slot></slot>\
</main>\
<footer>\
<slot name='footer'></slot>\
</footer>\
</div>"
})
var app = new Vue({
el: '#app'
})
</script>
动态插槽
<my-component>
<template v-slot:[dynamicSlotName]>
...
</template>
</my-component>
<div id="app">
<my-component>
<template v-slot:[slotname]>
这是header组件
</template>
</my-component>
</div>
<script>
Vue.component('my-component',{
template: `<div>
<slot name="header">
</slot>
</div>`
})
var app = new Vue({
el: '#app',
data: {
slotname: 'header'
}
})
</script>
作用域插槽
❝作用域插槽是一种特殊的 slot,使用可以复用的模板来替换已经渲染的元素
❞
作用域插槽是父组件从子组件中获取数据
- 把需要传递到的内容绑到子组件
<slot> - 在父组件用 v-slot 设置一个值来定义我们提供插槽的名字
注意:
- 只有提供的是默认插槽时,组件标签才能当作插槽模板来使用。
<my-component v-slot:defalut='slotProps'></my-component>
- 具名插槽使用template作为插槽模板使用
<template v-slot:other='slotProps'></template>
- 出现多个插槽时,都用template作为插槽模板
<my-component>
<template v-slot:default='slotProps'></template>
<template v-slot:other='slotProps'></template>
</my-component>
<div id="app">
//2.带值的 v-slot 来定义我们提供的插槽 prop 的名字。
<my-component v-slot:default='slotProps'>
{{slotProps.userText.firstName}}
</my-component>
</div>
<script>
Vue.component('my-component',{
template: "<div>\
//1.绑定在<slot>上的属性被称为插槽prop。
<slot :userText='user'>{{user.lastName}}</slot>\
</div>",
data: function(){
return {
user: {
firstName: 'li',
lastName: 'bai'
}
}
}
})
var app = new Vue({
el: '#app',
})
</script>
访问 slot
❝vm.$slots 用来访问被插槽分发的内容
❞
vm.$slots用来访问插槽分发的内容,vm.$slots.header能找到v-slot:header中的内容。。
<div id="app">
<my-component>
<template v-slot:header>
<h1>Hello</h1>
</template>
<p>vue</p>
<template v-slot:footer>
<p>world</p>
</template>
</my-component>
</div>
<script>
Vue.component('my-component',{
template: "<div>\
<header>\
<slot name='header'></slot>\
</header>\
<main>\
<slot></slot>\
</main>\
<footer>\
<slot name='footer'></slot>\
</footer>\
</div>",
mounted: function(){
var header = this.$slots.header;
console.log(header);
},
//将获取到的header的值插入到div中
render(createElement){
var header = this.$slots.header;
return createElement('div',[
createElement('header',header)
])
}
})
var app = new Vue({
el: '#app'
})
</script>
组件高级用法--动态组件
❝is 用于动态组件
❞
<div id="app">
<component :is='thisView'></component>
<button @click='handleView("A")'>A</button>
<button @click='handleView("B")'>B</button>
<button @click='handleView("C")'>C</button>
</div>
<script>
Vue.component('compA',{
template: '<div>我是一个组件A</div>'
})
Vue.component('compB',{
template: '<div>我是一个组件B</div>'
})
Vue.component('compC',{
template: '<div>我是一个组件C</div>'
})
var app = new Vue({
el: '#app',
data: {
thisView: 'compA'
},
methods: {
handleView: function(tag){
this.thisView = 'comp' + tag
}
}
})
</script>
在动态组件上使用 keep-alive
❝保持组件状态,避免反复重复渲染导致的性能问题
❞
<div id="app">
<keep-alive>
<my-component :is='thisView'></my-component>
</keep-alive>
<button @click="handle('A')">A</button>
<button @click="handle('B')">B</button>
<button @click="handle('C')">C</button>
</div>
<script>
Vue.component('compA',{
template: '<div>\
one:<input type="radio"/>\
two:<input type="radio"/>\
</div>'
})
Vue.component('compB',{
template: '<div>我是一个组件B</div>'
})
Vue.component('compC',{
template: '<div>我是一个组件C</div>'
})
var app = new Vue({
el: '#app',
data: {
thisView: 'compA'
},
methods: {
handle: function(tag){
this.thisView = 'comp' + tag
}
}
})
</script>
点击 A 按钮选择一个 radio,点击其它按钮再返回 A,radio 仍是未选择状态。使用 keep-alive 保证返回 A 按钮,radio 仍保持是你选择的状态。