Vue 基础 - 组件
组件
使用组件可提高代码的复用性
命名规则
vue组件中camelCased(驼峰式)命名与kebabcase(短横线命名):
- 在html中,
myMessage
和mymessage
是一致的,因此在组件中的html中使用必须使用kebabcase(短横线)命名。不允许使用驼峰! - 在组件中, 父组件给子组件传递数据必须用短横线。在template中,必须使用驼峰命名方式,若为短横线命名则会报错;
- 在组件的data中,用this.XXX引用时只能是驼峰命名方式,若为短横线命名则会报错。
组件注册
全局注册(所有vue对象都可以使用,但权限太大,容错率低)
<div id="app">
<my-component></my-component>
</div>
<script>
Vue.component(
'my-component', {
template:'<div>我是组件的内容</div>' // 组件中的内容会被模板替换
}
)
var app = new Vue({
el: "#app",
data: {
}
})
</script>
局部注册(Vue对象中可用)
var app = new Vue({
el: '#app',
components:{
'my-component':{
template: '<div>我是组件的内容</div>'
}
}
})
vue组件模板在某些情况下会受到HTML标签的限制:此时可以使用is属性来挂载组件:
<!-- 比如<table>中只能有<tbody>,<tr>,<td>等元素,所以直接在table中使用组件是无效的, -->
<table>
<tbody is="my-component"></tbody>
</table>
- 必须使用小写字母加“”命名组件(child、mycomponnet);
- template中的内容必须被一个DOM元素包括,也可以嵌套;
- 在组件的定义中,可以使用除了template之外的其他选项:data、computed、methods...
- data必须是一个方法:
// ...
'btn-component': {
template: '<button @click="count++">'{{count}}</button>,
data: function () {
return { // 专属于组件的对象
count: 0
}
}
}
数组验证
Vue中验证的数据类型有:
- String
- Number
- Boolean
- Object
- Array
- Function
<div id="app">
<type-component :a="a" :b="b" :d="d" :f="f" :g="g"></type-component>
</div>
<script>
// 数据验证组件
Vue.component('typeComponent', {
props: {
a: Number,
b: [String, Number], // 传入的b只允许是String或Number
c: {
type: Boolean,
default: true
},
d: {
type: Number,
required: true
},
e: {
type: Array,
default: function () {
return [];
}
},
f: {
validator: function (value) {
return value > 10;
}
},
g: {
type: Function
}
},
template: '<div>{{a}} - {{b}} - {{d}} - {{f}}</div>',
})
let app = new Vue({
el: "#app",
data: {
a: 1, b: 567, d: 789, f: 99, g: 1111
}
})
</script>
组件通信
父组件向子组件传递数据
- 在组件中使用
props
来从父亲组件接收参数,在props中定义的属性,都可以在组件中直接使用; - 组件中
propps
来自父级,而data
return的数据就是组件自己的数据,两种情况作用域就是组件本身,可以在template,computed,methods中直接使用; -
props
可以设置字符串数组或对象,以使用v-bind
动态绑定父组件来的内容。
父组件data: {parentMsg: "Hello World!"}
-> 子组件<bind-component v-bind:msg="parentMsg"></bind-component>
-> 子组件"bind-component": {props: ["msg"]}
-> 子组件"child-component": {template: "<div>{{msg}}</div>"}
<div id="father" style="border: 2px solid chartreuse; height: 160px">
<h5 style="text-align: center">父组件</h5>
<child-component msg="来自父组件的内容"></child-component> <!-- 父组件向子组件传递数据 -->
<input type="text" v-model="parentMsg">
<bind-component v-bind:msg="parentMsg"></bind-component> <!-- 父组件的parentMsg绑定在子组件的msg中 -->
</div>
<script>
let app = new Vue({
el: "#father",
data: {
parentMsg: "Hello World!"
},
components: {
"child-component": {
template: "<div>{{msg}}</div>",
props: ["msg"],
},
"bind-component": {
template: "<div>{{msg}}</div>",
props: ["msg"],
}
}
})
</script>
单向数据流:
- 通过
props
传递数据是单向的,父组件数据变化时会传递给子组件,但不能反过来; - 单向传递的目的是尽可能将父子组件解稿,避免子组件无意中修改了父组件的状态。
当父组件传递初始值进来,子组件将它作为初始值保存起来,在自己的作用域下可以随意使用和修改:
<div id="app">
<my-component msg="父组件传递到子组件的数据"></my-component>
</div>
<script>
// 注册组件
Vue.component('my-component', {
props: ['msg'], // 将父组件的数据传递进来,并在子组件中用props接收
template: '<div>子组件</div>',
data: function () {
return {
count: this.msg // 将传递进来的数据通过初始值保存起来
// props中的值可以通过this.XXX直接获取
}
}
})
let app = new Vue({
el: "#father",
data: {}
})
</script>
prop
作为需要被转变的原始值传入:
<div id="app">
<input type="text" v-model="width">
<my-comp :width="width"></my-comp>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
width: '100'
},
components: {
'my-comp': {
props: ['width'],
template: '<div :style="style">{{width}}px</div>',
computed: {
style: function () {
return {
width: this.width + 'px',
background: 'red'
}
}
}
}
}
})
</script>
子组件向父组件传递数据
子组件<button @click='increase'>+1</button>
-> 子组件this.$emit('change', this.count)
-> 父组件<child @change="handldTotal"></child>
-> 父组件handldTotal: function(value) {this.total = value}
<div id="app">
{{total}}
<child @change="handldTotal"></child> <!-- 也可以在组件中使用v-model的方法实现 -->
<!--<child v-model="total"></child>-->
</div>
<script>
Vue.component('child', {
template: "<div><button @click='increase'>+1</button></div>",
methods: {
increase: function () {
this.count += 1
this.$emit('change', this.count) // 子组件向父组件发出发生change事件的通知
// this.$emit('input', this.count) // 使用v-model时指定为input事件通知
}
},
data: function () {
return {
count: 200
}
}
})
let app = new Vue({
el: "#app",
data: {
total: 200
},
methods: {
handldTotal: function(value) {
this.total = value
}
}
})
</script>
-
$emit
实际上会触发一个input
事件, 其后的参数就是传递给vmodel绑定的属性的值 -
vmodel
其实是一个语法糖,这背后做了两个操作:
i.vbind
绑定一个value
属性
ii.von
指令给当前元素绑定input
事件
非父组件之间的通信
可以使用一个空的Vue实例作为中央事件总线:
<div id="app">
<a-component></a-component>
<b-component></b-component>
<br>
<child-component></child-component>
{{msg}}
</div>
<script>
Vue.component('a-component', {
template: '<div style="width: 100px; height: 100px; border: 1px solid black"><button @click="handle">点击向B组件传递数据</button></div>',
data: function () {
return {
a: '来自A组件的内容'
}
},
methods: {
handle: function () {
this.$root.bus.$emit('send', this.a)
}
}
})
Vue.component('b-component', {
template:'<div style="width: 100px; height: 100px; border: 1px solid black">{{b}}</div>',
data: function() {
return {
b: ""
}
},
created: function () {
let self = this
this.$root.bus.$on('send', function (value) {
self.b = value
})
}
})
Vue.component('child-component', {
template: '<button @click="setFatherData">通过点击修改父亲的数据</button>',
methods: {
setFatherData: function () {
this.$parent.msg = '数据已修改'
}
}
})
let app = new Vue({
el: "#app",
data: {
bus: new Vue(),
msg: '数据未修改',
formchild: '未取得数据'
},
methods: {
getChildData: function() {
this.formchild = this.$refs.c.msg;
}
}
})
</script>
使用Slot分发内容
让组件可以组合的过程被称为内容分发,使用特殊的slot
元素作为原始内容的插槽。
编译的作用域
<child-component>
{{message}}
</child-component>
其中message
应该绑定到父组件的数据,组件作用域简单地说是:
- 父组件模板的内容在父组件作用域内编译;
- 子组件模板的内容在子组件作用域内编译。
插槽的用法
将父组件的内容与子组件相混合,从而弥补了视图的不足:
<div id="app">
<my-component>
<p>我是父组件的内容</p>
<!-- 最外层组件内部的所有内容都由最外层组件控制 -->
<!-- 如果没有使用slot,则子组件无法在其内部插入内容 -->
</my-component>
</div>
<script>
Vue.component('my-component', {
template:
`<div>
<slot>如果父组件没有插入内容,我就作为默认出现</slot>
</div>`
})
new Vue({
el: "#app"
})
</script>
其中<slot>
标签中可以指定name
,称为“具名插槽”,对应DOM标签的slot
属性:
<p slot="footer">底部</p>
// ...
template: `
<div class="footer">
<slot name="footer"></slot>
</div>`
作用域插槽
使用一个可以复用的模板来替换已经渲染的元素
从子组件获取数据
<div id="app">
<my-component>
<template slot="abc" slot-scope="prop"> <!-- template是不会被渲染的,2.5.0版本后不需要写template -->
{{prop.text}} <!-- text在slot中定义 -->
</template>
</my-component>
</div>
<script>
Vue.component('my-component', {
template: `
<div>
<slot text="来自子组件的数据" name="abc"></slot>
</div>
`
})
new Vue({
el: "#app"
})
</script>
访问插槽
通过this.$slots.name
可以访问名称为name
的插槽
mounted:function () {
// 访问插槽
var header = this.$slots.header;
var text = header[0].elm.innerText;
var html = header[0].elm.innerHTML;
console.log(header)
console.log(text)
console.log(html)
}
动态组件
Vue提供了component元素用来动态的挂载不同的组件,使用is特性实现。
<div id="app">
<!-- 点击不同按钮实现切换不同组件 -->
<component :is="thisView"></component>
<button @click="handleView('A')">1</button>
<button @click="handleView('B')">2</button>
<button @click="handleView('C')">3</button>
</div>
<script>
Vue.component('compA', {
template: '<div>JavaScript</div>'
})
Vue.component('compB', {
template: '<div>CSS</div>'
})
Vue.component('compC', {
template: '<div>HTML</div>'
})
let app = new Vue({
el: "#app",
data: {
thisView: "compA"
},
methods: {
handleView: function (tag) {
this.thisView = "comp" + tag;
}
}
})
</script>