上一篇:vue组件化开发
在上一篇文章中,我们申明了一点,那就是组件之间是独立的,除非构建了通信。
组件间为什么要构建通信?
在单一文件中写上整个网站的代码逻辑,这无疑使得维护成本巨大。所以我们选择了组件化开发,把组件间独立起来,这样谁也不干涉谁,是谁的逻辑就在谁的文件中实现,最后串在一起,一个网站内容就大功告成了。那父子组件的通信有什么用呢?
我们不妨设想一个场景:在点外卖时,当我们选中某个餐品,下面的计价开始变化,甚至还会计算一下是否满配送,在淘宝购物车中,当我们清空一些商品时,下面也会有总价的变化,或者如果我们想要两个相同组件却拥有不同的反馈,比如两个按钮,一个按钮为提交,一个按钮为回退,但他们来自同一个组件,该怎么为他们添加事件呢?
以上的所有情况我们都可以通过父级组件为子组件提供数据来完成,通过props我们可以为子组件添加Number,String,Object,Array,甚至Function。并且,增加了组件间的通信也表现出了vue的弹性,我们是可以根据自己意愿来添加组件联系的。
组件的通信实现
想要实现组件间的通信,得有一方得发出信息,另一方接收信息,然后返回信息使对方接收到信息,这就是双向通信,但是不是每个组件都是双向通信,我们也可以父级组件单传数据,也可以使子组件单传数据给父组件。
父组件单传子组件
父组件
<template><show-info name="Mike" :age="18" :height="1.88" />
</template><script>import ShowInfo from './ShowInfo.vue'
export default {components:{ShowInfo,}
}
</script><style scoped></style>
子组件
<template><div class="infos"><h2>姓名:{{name}}</h2><h2>年龄:{{age}}</h2><h2>身高:{{height}}</h2></div>
</template><script>
export default {props:["name","height","age"],data(){return{}},
}
</script><style scoped></style>
首先,在传输数据前,我们是不是得对一个暗号?这个暗号就是属性名,父组件动态绑定的属性名要与子组件props中的一致,无关顺序。子组件获得来自父组件的数据后就可以使用了。当然,props不只有数组表达形式,还有对象形式。
<template><div class="infos"><h2>姓名:{{name}}</h2><h2>年龄:{{age}}</h2><h2>身高:{{height}}</h2></div>
</template><script>
export default {props:{name:String,height:Number,age:Number},data(){return{}},}
</script><style scoped></style>
props对象形式中支持我们为传入的数据类型进行一个限制,但是这种限制比较弱,只会提醒一下你,而不会报错。props还有一种形式,这种形式更加完备,我们可以为传输的数据添加默认值,这样即使父级组件没有传入数据也会正常渲染。
<template><div class="infos"><h2>姓名:{{name}}</h2><h2>年龄:{{age}}</h2><h2>身高:{{height}}</h2></div>
</template><script>
export default {props:{name:{type:String,default:"我是默认值"},height:{type: Number,default: 0},age:{type: Number,default: 0},},data(){return{}},
}
</script><style scoped></style>
非props
形象来说,就是没对上暗号的那些数据该怎么办?
在vue中,这些数据并不会直接丢掉,而是挂在子组件的根节点上,如果有两个根节点会发生什么?
什么都不会发生,会报个错然后让你选取一个根节点来挂载非props数据。
我们可以使用$attrs来访问所有的非props的属性。
子组件单传父组件
自定义事件
App.vue
<template><h2>counter:{{ counter }}</h2><add-counter @add="addBtnClick"></add-counter><sub-counter @sub="subBtnClick"></sub-counter>
</template><script>
import AddCounter from './AddCounter.vue'
import SubCounter from './SubCounter.vue'
export default {components:{AddCounter,SubCounter},data(){return {counter:0}},methods:{addBtnClick(count){this.counter += count;},subBtnClick(count){this.counter -= count;}}
}
</script><style scoped></style>
addCounter.vue
<template><div class="add"><button @click="addCounter(1)">+1</button><button @click="addCounter(5)">+5</button><button @click="addCounter(10)">+10</button></div>
</template><script>
export default {emits:["add"],methods:{addCounter(count){console.log("btnClick",count);//第一个参数为事件名称,第二个参数为传递参数this.$emit("add",count);}}
}
</script><style scoped></style>
subCounter.vue
<template><div class="sub"><button @click="subCounter(1)">-1</button><button @click="subCounter(5)">-5</button><button @click="subCounter(10)">-10</button></div>
</template><script>
export default {emits:["sub"],methods:{subCounter(count){console.log("btnClick",count);this.$emit("sub",count);}}
}
</script><style scoped></style>
先来简述一下上述代码功能:
我们分别有+1,+5,+10的三个按钮以及与之对应的减法按钮,当我们点击这些按钮后,位于父组件的counter发生相应的变化。
首先,我们想到的就是在子组件addCounter中创建一个方法increment,传入参数,可以控制为counter加一定的值,再在subCounter中创建一个方法decrement,传入参数,控制counter减一定的值。而counter我们可以通过父组件传过来,计算后的counter在写个方法让父组件监听,父组件得到值后再赋值给counter渲染。
这个方法是可行的。代码如下:
App.vue
<template><div class="app"><h2>{{ counter }}</h2><increment :counter="counter" @back="getBackValue"></increment></div>
</template><script>
import Increment from './Increment.vue';
export default{components:{Increment},data(){return{counter:0}},methods:{getBackValue(res){this.counter = res}}
}
</script><style scoped></style>
increment.vue
<template><div class="add"><button @click="addCounter(1)">+1</button><button @click="addCounter(5)">+5</button><button @click="addCounter(10)">+10</button></div>
</template><script>
export default {emits:["back"],props:["counter"],data(){return {}},methods:{addCounter(count){let value = this.$props.counter;value += countconsole.log("Backing")this.$emit("back",value)},backValue(){}}
}
</script><style scoped></style>
这样的确能实现目的,但是却不建议这么做,这相当于是把运算过程放在了子组件中,仅仅是把结果返回给父组件。但在实际开发中,我们不一定只开发这么简单的功能,运算或许没这么简单,况且,按下+1按钮给我返回1才符合逻辑吧?我们并不需要子组件干这么多不属于它的活。
下面是另外的一种思路:
对于子组件,不需要父组件给我任何数据,我只需要知道用户按下了哪一个按钮任何返回那个按钮的数值,这样我只需要在点击按钮中释放事件并且把数值传给释放事件中,父组件只需要监听到我这个事件,那任务就结束了。父组件接到子组件传来的值,这时,它可以对这个值做任何操作,这就使得我们有更多的操作空间。
而这就对应着一开始的代码,对比两种思路的代码,你会发现第二种思路的方式组件之间更加独立,至少我们不需要改逻辑的时候跑到子组件的方法中改。
props接收函数的情况
父级组件
<template><div><h1>我是父组件</h1><ChildComponent :handle-click="methodA" method-name="methodA" /><ChildComponent :handle-click="methodB" method-name="methodB" /></div>
</template><script setup>
import ChildComponent from './components/ChildComponent.vue';const methodA = (data) => {console.log('methodA 被调用:', data);
};const methodB = (data) => {console.log('methodB 被调用:', data);
};</script>
子组件
<template><div><button @click="callMethod">点击我,调用父组件的方法</button></div>
</template><script setup>
import { defineProps } from 'vue';const props = defineProps({handleClick: {type: Function,required: true,},methodName: {type: String,required: true,},
});function callMethod() {props.handleClick("一些子组件的数据")
}</script>
这结合了后面composition api的内容,但大体是一样的,这里我们做出的效果就是相同的两个组件拥有不同的方法,我们向子组件传入函数,这个用传统方法一直搞不出来,难受。最后只能用composition api来替代了,我有时间再试试。回到正题,我们传入methodA,子组件接收到这个函数,当点击按钮时,就会调用接收的函数。