使用Vue封装一个radio组件、RadioGroup组件
一、封装radio组件
- radio组件基本框架和样式:
<template> <label class="one-radio" :class="{'is-checked':label == value}"> <span class="one-radio_input"> <!-- --><input type="radio" class="one-radio_label" :value="label" :name="name"v-model="model"> <!-- --> </span> <span class="one-radio_label"> <slot></slot> <template v-if="!$slots.default">{{label}}</template> </span> </label></template><script>// 定义一个value属性,value值是接收到的labelexport default { name: 'oneRadio', data(){ return{ } }, props:{ label: { type: String, default:'0' }, value: { type: String, default: '' }, name :{ type: String, default: '' } }, computed: { // 如果用双向绑定一个计算属性,必须提供一个get和set;需要写成一个对象 model:{ get(){ // model的值是父组件传过来的value console.log('value'); return this.value }, set(value){ // 触发父组件给当前组件注册的input事件 this.$emit('input',value) } } }}</script><style lang="scss" scoped>.one-radio{ color: #606266; font-weight: 500; line-height: 1; position: relative; cursor: pointer; display: inline-block; white-space: nowrap; outline: none; font-size: 14px; margin-right: 30px; -moz-user-select: none; -webkit-user-select: none; -moz-user-select: none; .one-radio_input{ white-space: nowrap; cursor: pointer; outline: none; display: inline-block; line-height: 1; position: relative; vertical-align: middle; .one-radio_inner{ border: 1px solid #dcdfe6; border-radius: 100%; width: 14px; height: 14px; background-color: #fff; position: relative; cursor: pointer; display: inline-block; box-sizing: border-box; &:after{ width: 4px; height: 4px; border-radius: 100%; background-color: #fff; content: ""; position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%) scale(0); transition: transform .15s ease-in; } } .one-radio_original{ opacity: 0; outline: none; position: absolute; z-index: -1; top: 0; left: 0px; right: 0; bottom: 0; margin: 0; } } .one-radio_label{ font-size: 14px; padding-left: 10px;; } } // 选中的样式 .one-radio.is-checked{ .one-radio_input{ .one-radio_inner{ border-color: #409eff; background-color: #409eff; &:after{ transform: translate(-50%,-50%) scale(1); } } } .one-radio_label{ color:#409eff; } }</style>
- radio组件的数据双向绑定:
需要父组件传递label值和value值,其中value值使用v-model来绑定。
<one-radio v-model="gender" label="0">男</one-radio> <one-radio v-model="gender" label="1">女</one-radio>
子组件接收数据后,对数据进行处理:
①当radio组件被点击时,绑定的数据应该变为该组件的label值,因此我们将子组件中的input标签的value属性绑定为传入的label值;并声明一个计算属性model双向绑定到input组件上(model通过get获取值–>父组件传过来的value,通过set方法将值回调给父组件)。
②改变样式:
当我们在点击radio组件时,让被选中的组件添加选中样式,可以通过label和value的比较来判断,:class="{'is-checked':label == value}"
,如果相同,则显示选中样式。
二、封装RadioGroup组件
因为存在两种①radio不被RadioGroup包裹和②radio被RadioGroup包裹的情况。第二种情况,如果radio被RadioGroup包裹时,我们在上面封装的组件就不能用了。因此,我们需要考虑radio是否被RadioGroup所包裹。
为了知晓radio是否被包裹,可以使用provide方法(在祖先组件中负责向子孙组件发送数据)和inject(在子孙组件中负责接收来自祖先组件的数据)。
在redio组件中,添加inject 来接收传入的值;
而且,我们想要拿到value值,并一定是从父组件中拿到(使用this.value),有可能是从祖先组件中拿到value值,因此,我们在computed计算属性中添加一个isGroup方法,用于判断是否radio被radioGroup所包裹。
当radio被radioGroup包裹时,不能用this.value,只能用this.radioGroup.value,因此,return this.isGroup ? this.RadioGroup.value : this.value;
,根据isGroup来决定如何获取value的值,修改值set中相应地也要进行修改,this.isGroup ? this.RadioGroup.$emit('input',value) : this.$emit('input',value)
。
<template> <label class="one-radio" :class="{'is-checked':label == value}"> <span class="one-radio_input"> <!-- --><input type="radio" class="one-radio_label" :value="label" :name="name"v-model="model"> <!-- --> </span> <span class="one-radio_label"> <slot></slot> <template v-if="!$slots.default">{{label}}</template> </span> </label></template><script>// 定义一个value属性,value值是接收到的labelexport default { name: 'oneRadio', data(){ return{ } }, inject: { RadioGroup:{ default: '' } }, computed: { // 如果用双向绑定一个计算属性,必须提供一个get和set;需要写成一个对象 model:{ get(){ // model的值是父组件传过来的value // return this.value return this.isGroup ? this.RadioGroup.value : this.value; }, set(value){ // 触发祖先组件RadioGroup给当前组件注册的input事件 //this.$emit('input',value) this.isGroup ? this.RadioGroup.$emit('input',value) : this.$emit('input',value) } }, isGroup(){ // 用于判断radio是否被radioGroup所包裹(使用2个感叹号,将其改为布尔值) return !!this.RadioGroup } }}</script>
最终效果:
这里附上 完整代码
- radioGroup.vue:
<template> <div class="one-radio-group"> <slot></slot> </div></template><script>export default { name:'HmRadioGroup', provide(){ return { RadioGroup: this } }, props:{ value: null }, }</script><style></style>
- radio.vue
<template> <label class="one-radio" :class="{'is-checked':label === model}"> <span class="one-radio_input"> <!-- --><input type="radio" class="one-radio_label" :value="label" :name="name"v-model="model"> <!-- --> </span> <span class="one-radio_label"> <slot></slot> <template v-if="!$slots.default">{{label}}</template> </span> </label></template><script>// 定义一个value属性,value值是接收到的labelexport default { name: 'oneRadio', data(){ return{ } }, inject: { RadioGroup:{ default: '' } }, props:{ label: { type: String, default:'0' }, value: { type: String, default: '' }, name :{ type: String, default: '' } }, computed: { // 如果用双向绑定一个计算属性,必须提供一个get和set;需要写成一个对象 model:{ get(){ // model的值是父组件传过来的value console.log('value'); // return this.value return this.isGroup ? this.RadioGroup.value: this.value }, set(value){ // 触发父组件给当前组件注册的input事件 // this.$emit('input',value) this.isGroup ? this.RadioGroup.$emit('input',value) : this.$emit('input',value) } }, isGroup(){ // 用于判断radio是否被radioGroup所包裹(使用2个感叹号,将其改为布尔值) return !!this.RadioGroup } }}</script><style lang="scss" scoped>.one-radio{ color: #606266; font-weight: 500; line-height: 1; position: relative; cursor: pointer; display: inline-block; white-space: nowrap; outline: none; font-size: 14px; margin-right: 30px; -moz-user-select: none; -webkit-user-select: none; -moz-user-select: none; .one-radio_input{ white-space: nowrap; cursor: pointer; outline: none; display: inline-block; line-height: 1; position: relative; vertical-align: middle; .one-radio_inner{ border: 1px solid #dcdfe6; border-radius: 100%; width: 14px; height: 14px; background-color: #fff; position: relative; cursor: pointer; display: inline-block; box-sizing: border-box; &:after{ width: 4px; height: 4px; border-radius: 100%; background-color: #fff; content: ""; position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%) scale(0); transition: transform .15s ease-in; } } .one-radio_original{ opacity: 0; outline: none; position: absolute; z-index: -1; top: 0; left: 0px; right: 0; bottom: 0; margin: 0; } } .one-radio_label{ font-size: 14px; padding-left: 10px;; } } // 选中的样式 .one-radio.is-checked{ .one-radio_input{ .one-radio_inner{ border-color: #409eff; background-color: #409eff; &:after{ transform: translate(-50%,-50%) scale(1); } } } .one-radio_label{ color:#409eff; } }</style>
- App.vue 中使用
<template> <div id="app"> <one-radio v-model="gender" label="0">男</one-radio> <one-radio v-model="gender" label="1">女</one-radio> <hm-radio-group v-model="gender"> <one-radio label="0">男</one-radio> <one-radio label="1">女</one-radio> </hm-radio-group> </div></template><script>export default { name: 'App', components: { }, data(){ return { money: 100, visible: false, active: true, password: '', swFlag: false, swFlag2: false, gender:'' } } , mounted(){ }, methods:{ // 父组件在使用时定义自己的点击事件,其本质是子组件中的点击事件触发父组件中的点击事件 getInfo() { console.log('获取信息!!'); // 获取子传父的信息 }, // 父组件控制dialog的显示与隐藏 // switchDialog(value){ // this.visible = value // }, db(value){ this.money = value } }}</script><style></style>