[TOC] >[success] # CheckboxGroup ~~~ 1.无论是使用iview 还是element ui,都可以发现在'checkbox'这里他们都会提供一个组件叫 'CheckboxGroup',会将'checkbox' 数据统一管理,我们来以'iview' 为例: <template> <i-checkbox-group v-model="multiple"> <i-checkbox label="option1">选项 1</i-checkbox> <i-checkbox label="option2">选项 2</i-checkbox> <i-checkbox label="option3">选项 3</i-checkbox> <i-checkbox label="option4">选项 4</i-checkbox> </i-checkbox-group> </template> <script> export default { data () { return { multiple: ['option1', 'option3'] } } } </script> 所以这章节就是封装一个'CheckboxGroup' 组件 ~~~ >[info] ## 上个章节分装的checkbox组件和实际原生的区别 ~~~ 1.在封装之前,给先知道原生的'checkbox' 和我们上个章节封装的不同点 ~~~ >[danger] ##### 原生 ~~~ 1.这里直接用了vue官方文档的案例 2.可以发现原生"checkbox" 本身绑定的就是数组,是不是有点懵了,上个章节我们封装的'checkbox'组件绑定 的只能是'String,Number,Boolean'这三种类型,其实上个章节案例将'value' 这个绑定值改为数组,在做一些 略微逻辑跳转也可以实现类似原生的方法 ~~~ ~~~ <div id='example-3'> <input type="checkbox" id="jack" value="Jack" v-model="checkedNames"> <label for="jack">Jack</label> <input type="checkbox" id="john" value="John" v-model="checkedNames"> <label for="john">John</label> <input type="checkbox" id="mike" value="Mike" v-model="checkedNames"> <label for="mike">Mike</label> <br> <span>Checked names: {{ checkedNames }}</span> </div> ~~~ ~~~ new Vue({ el: '#example-3', data: { checkedNames: [] } }) ~~~ >[info] ## 为了可以和原生一样绑定数组写 -- CheckboxGroup 组件 ~~~ 1.写组件从三个方面入手'props','events','slots',根据iview组件我们也可以简单分析出来, 首先使用的时候用的是'v-model',那就一定有两个一个是'value',一个是'input'事件, 再就是有一个'solt' 用来放置封装好的'checkbox'组件 ~~~ >[danger] ##### 改进我们的checkbox 组件让更像原生用法 ~~~ 1.现在我们要将我们上一个章节的'checkbox'可以也绑定数组,对照iview我们大体设计如下: ~~~ * iview ~~~ <i-checkbox label="option1">选项 1</i-checkbox> ~~~ * 我们大体猜想 ~~~ 1.iview 中'<i-checkbox label="option1">选项 1</i-checkbox>' label 作为封装组件的'props'中的属性传递给 'label',在就保证这一组的input中'v-model'绑定都是相同的数组 ~~~ ~~~ <input type="checkbox" :disabled="disabled" :value="label" v-model="model" @change="change" /> ~~~ >[danger] ##### 改造checkbox组件 ~~~ 1.我们将checkbox组件通过if分割成了两组,一组是我们之前的用绑定是'String,Number,Boolean'这三种类型, 一组是我们为了让他像原生一样绑定的是数组 2.我们现在可以确定一点的思路就是如果是最外层使用的是'CheckboxGroup' 组件,那将使用的就是我们的数组 形式的input,这里在'mounted'声明周期使用'findComponentUpward'在组件通讯章节讲的写法,去找当前组件的 父级是否是使用'CheckboxGroup' 组件包裹来决定使用哪种模式的'checkbox' ~~~ ~~~ <template> <label> <span> <input v-if="group" type="checkbox" :disabled="disabled" :value="label" v-model="model" @change="change" /> <input v-else type="checkbox" :disabled="disabled" :checked="currentValue" @change="change" /> </span> <slot></slot> </label> </template> <script> import { findComponentUpward } from "@/lib/util.js"; export default { name: "iCheckbox", props: { label: { type: [String, Number, Boolean] }, disabled: { type: Boolean, default: false }, value: { type: [String, Number, Boolean], default: false }, trueValue: { type: [String, Number, Boolean], default: true }, falseValue: { type: [String, Number, Boolean], default: false } }, data() { return { model: [], group: false, parent: null, currentValue: this.value }; }, methods: { change(event) { if (this.disabled) { return false; } const checked = event.target.checked; this.currentValue = checked; const value = checked ? this.trueValue : this.falseValue; this.$emit("input", value); console.log(this.model); if (this.group) { this.parent.change(this.model); } else { this.$emit("on-change", value); } }, updateModel() { this.currentValue = this.value === this.trueValue; } }, mounted() { this.parent = findComponentUpward(this, "iCheckboxGroup"); if (this.parent) { this.group = true; } if (this.group) { // 关于updateModel 写法往下看,这里不用加因为下面父组件的生命周期 // 也做了同样的事感觉不用加 this.parent.updateModel(true); } else { this.updateModel(); } console.log(this.model); } }; </script> ~~~ >[danger] ##### 现在来分析我们的 CheckboxGroup 组件写法 ~~~ 1.上面有个'updateModel'方法具体思路,我们先思考一下首先,'CheckboxGroup' 组件插槽中的每一个 'checkedbox' 组件的'v-model'绑定的数组一定要是一个数组,这个数组就是'CheckboxGroup' 传入进来的数组 2.因此现在的思路就是使用我们组件通信章节说的'findComponentsDownward'向下找到所有在当前组件 'CheckboxGroup' 里面的每一个'checkedbox'组件,并且给他们的'v-model'绑定的数组值都赋值成'CheckboxGroup' 的数组。 3.在'updateModel'方法中有个地方需要说明一下: if (this.childrens) { // 这里的this 指代的是当前'CheckboxGroup' 组件,首先因为是一个实例所以肯定是一个对象 // 这里又使用了es6 解构获取 value 值,这个value值是谁呢? // 首先我们看使用 <checkbox-group v-model="multiple">,因此其实当前这个组件有个隐藏的props // 这个props 就是value 这个value 绑定的又是获取每一项的数组 // 因此将每一项'checkedbox'要管理的数组我们找到了,现在又有每一个子项的对象 // 将每一个子项对象中的'model ' 都绑定上这个统一的数组 const { value } = this; console.log(this); this.childrens.forEach(child => { child.model = value; if (update) { child.currentValue = value.indexOf(child.label) >= 0; child.group = true; } }); } ~~~ ~~~ <template> <div> <slot></slot> </div> </template> <script> import { findComponentsDownward } from "@/lib/util.js"; export default { name: "iCheckboxGroup", props: { value: { type: Array, default() { return []; } } }, data() { return { currentValue: this.value, childrens: [] }; }, methods: { updateModel(update) { this.childrens = findComponentsDownward(this, "iCheckbox"); if (this.childrens) { const { value } = this; console.log(this); this.childrens.forEach(child => { child.model = value; if (update) { child.currentValue = value.indexOf(child.label) >= 0; child.group = true; } }); } }, change(data) { this.currentValue = data; this.$emit("input", data); this.$emit("on-change", data); } }, mounted() { this.updateModel(true); }, watch: { value() { this.updateModel(true); } } }; </script> ~~~ >[danger] ##### 使用的效果 ~~~ <template> <div> {{multiple}} <checkbox-group v-model="multiple"> <checkbox label="option1">选项 1</checkbox> <checkbox label="option2">选项 2</checkbox> </checkbox-group> </div> </template> <script> import Checkbox from "@/components/checkbox/Checkbox.vue"; import CheckboxGroup from "@/components/checkbox/CheckboxGroup.vue"; export default { data() { return { multiple: ["option1"], }; }, components: { Checkbox, CheckboxGroup } }; </script> <style> </style> ~~~