组件(Component)是Vue中亮眼的部分,它可以扩展html元素,封装代码。每个组件里都包含了展现,功能和样式,每个页面可以根据自己的需要,使用不同的组件,这样有利于页面的扩展且十分灵活。
一、注册组件
注册组件:注册组件包括全局注册和局部注册两种方式。
1. 全局注册:使用Vue.component(tagName,options)来进行注册。其中tagName为组件名,不能是已经存在的html标签,最好是用带有"-"短横线的分隔符(<my-component>)或者是大写的驼峰(MyCompoent)来命名。在组件注册完成后,它就可以在父实例的模板中以自定义元素的形式来使用了。全局注册的组件它可以用在任何新创建的Vue根实例模板中。如下所示:
<di id="app">
<my-component1-name></my-component1-name>
</di>
<script>
//每个组件是独立的
Vue.component('my-component1-name', {
//选项
template: `<button>click</button>`,
})
let vm = new Vue({
el: '#app',
});
</script>
需要注意的是全局注册的行为必须在根Vue实例创建之前发生。
2. 局部注册:局部注册它是通过使用组件实例选项components来进行的,可以使组件仅在一个实例或者是组件的作用域中可用。它想在哪个组件中使用,那么就在哪个组件在进行注册。
<body>
<div id="app">
<!--3. 使用 在当前组件定义的模板中使用-->
<my-component-name></my-component-name>
</div>
</body>
<script>
//1. 注册,或者是导入一个组件
let tag={
template:'<div> davina </div>'
}
//2. 注册
new Vue({
el:'#app',
components:{
'my-component-name':tag
}
})
</script>
需要注意的是局部组件在其子组件中不可用,要用也是通过以下方式来进行使用。
let ComponentA = { };
let ComponentB = {
components: {
'component-a': ComponentA
},
// ...
}
一般的,我们在vue组件对象中,通过data来进行数据传递。当一个组件被定义时,data需要声明为返回一个初始对象的函数,因为组件可能用来创建多个实例,如果data还像原来一样仅仅是作为一个对象那所有的实例都将使用这一个数据对象。但如果data是函数返回的对象,那它就可以保证数据的独立性,每次创建一个实例后,能够调用用data函数,返回初始数据的一个全新副本数据对象了。
<body>
<div id="app">
<child></child>
</div>
</body>
<script>
let child = {
data() {
return {
name: '局部组件'
}
},
template: '<strong> {{name}}</strong>'
}
new Vue({
el: '#app',
components: {
'child': child
}
})
</script>
二、组件通信
组件通信有props&$eimt,$parent&$children,$attrs/$listeners 和provide/inject等方式。下面来分别进行介绍。
1、props验证和$emit
父组件通过自定义属性和props向子组件传递数据,子组件向父组件传递数据或父组件想使用子组件里的数据或者方法时,可以用到自定义事件和$emit。
下面我们通过一个小示例说明父组件App.vue与子组件childApp.vue如何进行相互之间的数据传递。App组件使用了childAPP组件,那么App称为父组件,childApp为子组件,它们是相互的概念。
// App.vue 父组件
<template>
<div>
<!-- 3.使用
前面为自定义名称,它便于子组件调用,后者是要传递数据名
把money这个变量对应的值通过childApp这个属性传递给childApp组件
-->
<h4>父亲有:{{money}}</h4>
<childApp :childApp="money" @myfn="fn"></childApp>
</div>
</template>
<script>
//1. 声明
import childApp from "./son1";
export default {
//当前组件的名字,可以不写,但写了不能是html
name: "App",
data() {
return {
name: "davina",
money: 100
};
},
components: {
// 2.注册
childApp: childApp
},
methods: {
fn(newMoney) {
console.log(arguments.this);
this.money = newMoney;
}
}
};
</script><style lang='less'></style>
// childApp 子组件
<template>
<div >
<h4>儿子获得父亲传递过来的值:{{childApp}}</h4>
<button @click="change()">点击修改</button>
</div>
</template>
<script>
export default {
name: "childApp",
//data mthods props components它们的属性名是不能重复的
props: ["childApp"],
methods:{
change(newMoney){
console.log('子向父传递');
this.$emit('myfn',300,)
}
}
};
</script>
<style></style>
prop是单向绑定,当父组件的属性变化时,将传递给子组件,但是不应该反过来。每当父组件进行更新时,它可以通过自定义属性和prop来向子组件传递数据,子组件的prop都会更新为最新值,这意味着不应该在子组件的内部改变prop。子组件它也可以通过自定义事件和$emit向父组件传递数据如果这样做了,最好要有警告。可以将prop看成是一个管道与管道之间的衔接口,这样数据才能向下流。
我们可以为组件的props指定验证规格,如果传入的数据不符,则Vue会发出警告。通过type来指定类型,required选项来声明这个参数是否必须传入;default选项来指定当父组件未传入参数时props变量的默认值;validator可以用来定义函数校验那些很复杂的规则。
Vue.component('test', {
//要用到对象形式,不能使用用字符串数组了
props: {
//基础类型检测
propA: Number,
//多种类型
propB: [Boolean, String],
//必须是字符串
propC: {
type: String,
required: true
},
// 有默认值
propD: {
type: Number,
default: 1
},
//自定义验证函数
propE: {
validator: function (val) {
return value > 0;
}
}
}
})
而对于爷孙之间是没有办法直接通信的,它可以看成两个父子组件之间的通信。son在中间起着关键性的作用。
// childApp 子组件
<template>
<div >
<h4>儿子获得父亲传递过来的值:{{childApp}}</h4>
<button @click="change()">点击修改</button>
<!--方法一: 给grandSon1绑定事件 -->
<!-- <grandSon1 :childApp="childApp" @changeMoney="changeMoney"></grandSon1> -->
<grandSon1 :childApp="childApp"></grandSon1>
</div>
</template>
<script>
//App和grandSon1通信
import grandSon1 from './grandSon1'
export default {
name: "childApp",
//data mthods props components它们的属性名是不能重复的
props: ["childApp"],
methods:{
change(){
console.log('子向父传递');
this.$emit('myfn',300,)
},
changeMoney(newMoney){
//把新值传递给父亲
this.$emit('myfn',newMoney);
}
},
components:{
grandSon1
}
};
</script>
<style></style>
------------------------------------------------------------
//garandSon1
<template>
<div>
<h4>孙子获得父亲传递过来的值:{{childApp}}</h4>
<button @click="changeMoney">点击修改</button>
</div>
</template>
<script>
export default {
props:{
childApp:{}
},
methods:{
changeMoney(){
//方法一:触发自己身上的事件
// this.$emit('changeMoney',400)
//方法二:一步到位,向上查找父亲,利用$parent,直接触发父亲绑定的事件
this.$parent.$emit('myfn',400)
}
}
}
</script>
<style ></style>
但是如果有多层的话,不管是方法一还是方法二都显得冗长,所以我们可以自己封装一个方法利用方法二的形式来自动向上进行父亲的查找
Vue.prototype.$dispatch = function(eventName, value) {
let parent = this.$parent; //先找第一层$parent
while (parent) {
parent.$emit(eventName, value); //触发方法
parent = parent.$parent; //向上进行查找
}
};
这个方法可以直接写在原型上,当main.js上有了这个方法后,grandSon1它只需要调用这个方法就可以进行查找了,示例如下:
methods: {
changeMoney() {
this.$dispatch("myfn", 400)
}
}
既然可以向上查找,那我们也可以封装一下方法向下进行查找,当父组件调用这个方法时,会触发所有的后代组件执行这个方法。
Vue.prototype.$broadcast = function broadcast(eventName, value) {
let children = this.$children;
function broad(children) {
children.forEach(child => {
child.$emit(eventName, value); //触发当前儿子上对应的方法
if (child.$children) {
//如果儿子下还有儿子继续查找
broad(child.$children);
}
});
broad(children); //先找自己的儿子
}
};
在有此情况下,我们需要对prop进行“双向绑定”,也就是实现在子组件中改变值,父组件中的值也能也确实变化。但这中又存在问题,因为子组件可以变更父组件,且在父组件和子组件都没有明显的变更来源。这时我们可以用到.sync修饰符和update:myPropName模式。其实.sync的作用就是在父级中给组件添加上一个v-on:update:xxx这样一个监听。如果我们绑定的是一个简单数据类型的时候,就可以使用它,它要配合$emit来一起使用。
如果不用.sync代码如下:
//parent.vue
<template>
<div>
<h4>父亲的值为:{{money}}</h4>
<son1 :money="money" @update:money="newValue=>money=newValue"></son1>
</div>
</template>
<script>
import son1 from './son1'
export default{
name: 'parent',
data(){
return {
money:10
}
},
components:{
son1
}
}
</script>
<style lang='less'></style>
---------------------------------------------
//son1.vue
<template>
<div>
<h4>儿子引用父亲的值:{{money}}</h4>
<button @click="changeCount">点击修改</button>
</div>
</template>
<script>
export default{
name: 'son1',
props:['money'],
methods:{
changeCount(){
this.$emit('update:money',30)
}
},
}
</script>
<style lang='less'></style>
但如果用.sync时,父组件如下,子组件还是用到$emit。需要注意属性名称,默认组件内部要触发update:myPropName规定写法。它只能用在简单数据类型。
<son1 :money.sync="money"></son1>
因为v-model它是等同于:value+@input事件的,所以上面的代码也可以写成下面这种:
//parent.vue
<template>
<div>
<h4>父亲的值为:{{money}}</h4>
<!-- <son1 :value="money" @input="newValue=>money=newValue"></son1> -->
<son1 v-model="money"></son1>
</div>
</template>
<script>
import son1 from "./son1";
export default {
name: "parent",
data() {
return {
money: 10
};
},
components: {
son1
}
};
</script>
<style lang='less'></style>
--------------------------------------------
son1.vue
<template>
<div>
<h4>儿子引用父亲的值:{{value}}</h4>
<button @click="changeCount">点击修改</button>
</div>
</template>
<script>
export default{
name: 'son1',
props:['value'],
methods:{
changeCount(){
this.$emit('input',100)
}
},
}
</script>
<style lang='less'></style>
从上面我们可以看到v-model和.sync的区别:它们二者都是实现组件与外部数据的双向绑定。v-model 可以看成是.sync 的一种体现。v-model它是绑定value值,监听input事件,它更倾向于value只能传递一个属性。而.sync 倾向于 ‘change’,是父组件获取子组件状态的一种快捷方式,是一种update操作,事件名称也相对固定 update:xx。
上面数据互相传递的方法是我们平时在项目中经常进行使用的方法,还有一些很偏,仅作了解。
$parent/$children:$parent获取当前组件父组件对应的实例或者是组件。那么实例上的任意属性我们都是可以获取到的。$children: 获取所有子组件,根据索引可以找到对应的子组件。就$children来说,它即不是响应式的,不能保证顺序,不能保证这是第几个儿子,所以最好不用。就this.$parent来说,直接能过父实例来进行数据之间的交互,这样做不安全,且不规范,极小情况下会这样直接修改父组件中的数据。
$attrs/$listeners:$attrs:它获取的是没有被props接收的那些属性。$listeners: 获取的是传递的自定义事件。
ref: 操作DOM的属性或者组件。通过this.refs来进行获取,只能获取到当前元素或者是组件。
可以直接通过this.refs.自定义名字来修改当前元素的属性
<h1 ref="test">test</h1>
console.log(this.$refs.test);
this.refs.test.style.background='red';
provide/inject: 祖先元素中通过provide来提供变量,那么它所有的后代组件都可以通过inject获取provide提供的属性了。它主要解决的是跨组件之间的通信问题。