前言
通常我们封装一个vue组件,比较常用的做法就是父组件传递 props 给子组件,子组件通过 emit 触发事件更新父组件的值。在vue中,一般的表单组件,可以直接用 v-modle 来进行双向绑定,如果我们要进一步封装表单组件,就需要自定义 v-model 指令了。
组件v-model默认行为
一个组件上的
v-model默认会利用名为value的 prop 和名为input的事件
利用value和input自定义v-model
my-checkbox.vue
<template>
<label class="ab-checkbox">
<span class="checkbox__input" :class="{ 'is-checked': model }">
<span class="checkbox__inner"></span>
<input
type="checkbox"
v-model="model"
class="checkbox__original"
@change="handleChange"
/>
</span>
<span class="checkbox__label" v-if="$slots.default || label">
<slot></slot>
<template v-if="!$slots.default">props: {{ label }}</template>
</span>
</label>
</template>
<script>
export default {
name: "AbCheckbox",
props: {
value: Boolean,
label: String,
},
data() {
return {};
},
computed: {
model: {
get() {
return this.value;
},
set(val) {
this.$emit("input", val);
},
},
},
created() {
if (this.checked) {
this.model = true;
}
},
methods: {
handleChange(e) {
this.$emit("change", e.target.checked);
},
},
};
</script>
<style lang="scss" scoped>
.ab-checkbox {
display: flex;
align-items: flex-start;
.checkbox__input {
position: relative;
display: flex;
align-items: center;
white-space: nowrap;
line-height: 1;
vertical-align: middle;
outline: 0;
cursor: pointer;
}
.checkbox__inner {
position: relative;
display: inline-block;
width: 14px;
height: 14px;
border: 1px solid #444df7;
border-radius: 2px;
box-sizing: border-box;
transition: border-color 0.25s cubic-bezier(0.71, -0.46, 0.29, 1.46),
background-color 0.25s cubic-bezier(0.71, -0.46, 0.29, 1.46);
z-index: 1;
&::after {
position: absolute;
top: 1px;
left: 4px;
width: 3px;
height: 7px;
content: "";
border: 1px solid #f84960;
border-left: 0;
border-top: 0;
transform: rotate(45deg) scaleY(0);
transition: transform 0.15s ease-in 0.05s,
-webkit-transform 0.15s ease-in 0.05s;
transform-origin: center;
box-sizing: content-box;
}
}
.checkbox__original {
opacity: 0;
outline: 0;
position: absolute;
margin: 0;
width: 0;
height: 0;
z-index: -1;
}
.checkbox__label {
display: inline-block;
padding-left: 0.5rem;
color: #a3ebed;
line-height: 14px;
font-size: 0.18rem;
}
.checkbox__input.is-checked .checkbox__inner::after {
transform: rotate(45deg) scaleY(1);
}
}
</style>
App.vue
<template>
<div>
<my-checkbox v-model="checked1" @change="handleChange1">同意</my-checkbox>
</div>
</<template>
<script>
export default {
name: 'App',
data() {
return {
checked1: true,
};
},
methods: {
handleChange1(val) {
console.log('data1: ', val)
}
}
}
</script>
利用checked和change自定义v-model
my-checkbox.vue
<template>
<label class="ab-checkbox">
<span class="checkbox__input" :class="{ 'is-checked': checked }">
<span class="checkbox__inner"></span>
<input
class="checkbox__original"
type="checkbox"
:checked="checked"
@change="handleChange"
/>
</span>
<span class="checkbox__label" v-if="$slots.default || label">
<slot></slot>
<template v-if="!$slots.default">{{ label }}</template>
</span>
</label>
</template>
<script>
export default {
name: "AbCheckbox",
// 父组件绑定的v-model值默认传给value,如果要使用checked,需要通过model显示声明checked
model: {
prop: 'checked',
event: 'change'
},
props: {
label: String,
checked: Boolean
},
data() {
return {};
},
computed: {
},
methods: {
handleChange(e) {
this.$emit("change", e.target.checked);
},
},
};
</script>
<style lang="scss" scoped>
.ab-checkbox {
display: flex;
align-items: flex-start;
.checkbox__input {
position: relative;
display: flex;
align-items: center;
white-space: nowrap;
line-height: 1;
vertical-align: middle;
outline: 0;
cursor: pointer;
}
.checkbox__inner {
position: relative;
display: inline-block;
width: 14px;
height: 14px;
border: 1px solid #444df7;
border-radius: 2px;
box-sizing: border-box;
transition: border-color 0.25s cubic-bezier(0.71, -0.46, 0.29, 1.46),
background-color 0.25s cubic-bezier(0.71, -0.46, 0.29, 1.46);
z-index: 1;
&::after {
position: absolute;
top: 1px;
left: 4px;
width: 3px;
height: 7px;
content: "";
border: 1px solid #f84960;
border-left: 0;
border-top: 0;
transform: rotate(45deg) scaleY(0);
transition: transform 0.15s ease-in 0.05s,
-webkit-transform 0.15s ease-in 0.05s;
transform-origin: center;
box-sizing: content-box;
}
}
.checkbox__original {
opacity: 0;
outline: 0;
position: absolute;
margin: 0;
width: 0;
height: 0;
z-index: -1;
}
.checkbox__label {
display: inline-block;
padding-left: 0.5rem;
color: #a3ebed;
line-height: 14px;
font-size: 0.18rem;
}
.checkbox__input.is-checked .checkbox__inner::after {
transform: rotate(45deg) scaleY(1);
}
}
</style>
App.vue
<template>
<div>
<my-checkbox v-model="checked2" @change="handleChange2">哈哈</my-checkbox>
</div>
</<template>
<script>
export default {
name: 'App',
data() {
return {
checked2: true,
};
},
methods: {
handleChange2(val) {
console.log('data2: ', val)
}
}
}
</script>