前言
这里分享一些自己在学习Vue组件的时候仿写过的一些组件,这篇是element-ul的Radio
,对于组件的实现话,主要以template、script两部分为主,style基本上都来自源码的样式,文章的内容我会在我实现的代码上进行注释我为什么要这样写,有些组件我分成残次写法和完整写法两种,可能看起来会有些凌乱,如果你有什么好的点子,也可以与我分享,我会学习并整合起来。
template
残次写法:在不实现element-ui文档的css样式的基础上实现单选框的效果
<template>
// 6.那么按照5的提示,我需要给整个按钮绑定事件来用来传递改变后的值,给label
// 绑定是运用冒泡的原理,在点击Radio组件的任意一个部分的时候,他都会触发
// 这个事件,避免用户点击的时候自己明明点到了,但是没有被选中,以此优化用户
// 体验
// 8.在7的基础上已经完成了label和value的相等,现在只需要给input选中添加条件
// 就完成了,也就是checked属性的值是true还是false
<label @click="trigger">
<input type="radio" :checked="value === label"/>
<span>
<slot>选项数据</slot>
</span>
</label>
</template>
完整写法:实现跟element-ui文档上面类似的效果
<template>
// 主体结构:
// a.对于element-ui的单选框而言,从外观上看需要一个能够选中的input,
// 以及一个放置在后面内容的标签,因为是行内元素,所以以span标签为
// 主
// label标签:
// a.单选框和多选框一般是行内元素,但是如果你想使用div这种标签然后使用
// display:block也可以
// b.label标签可以和input标签进行联动,之前分析过,当他们两者进行绑定
// 的时候,点击label标签等价于点击input标签,优化用户的体验感
// 第一个span标签:
// a.为了实现跟文档上面类似的样式,那么这里需要自己实现一个ckeckbox的
// 单选框
// b.input标签自带的checkbox属性仰视你很丑,所以需要将其display:none
// 隐藏起来
// c.然后自己使用一个span标签来模拟出一个小圆圈
// d.然后是否选中的话在文档中这个在很多地方是需要用到的,所以将其封装
// 为一个方法isChecked,方便调用
// 带有slot的span标签:
// a.因为这个地方是需要渲染位子内容的,但是文字内容又需要外界传递,所以
// 在文字渲染的这个位置就需要使用到插槽了,在slot插槽那个地方说到过,
// 插槽可以将传递的内容给接住,并将接住的内容渲染到插槽所在的这个位置。
<label class="co-radio" @click="trigger">
<span :class="['co-radio__input', { 'is-checked': isChecked }]">
<span class="co-radio__inner"></span>
<input type="radio" :checked="isChecked" />
</span>
<span class="co-radio__label">
<slot>选项数据</slot>
</span>
</label>
</template>
script
残次写法:
**element-ul的Radio的使用**
// 1.在el-radio标签上面看见了v-model,那么这个标签上绑定了一个value的属性
// 和一个input的事件,而且这个value属性必须是props才能完成数据的双向绑定
// 2.然后猜测在组件使用的时候是因为标签的label的值与value的值相等的时候,标
// 签才显示为选中,所以label的值也是动态的,所以也需要进行双向绑定,那么
// label的值也必须是props属性才可以。
// 3.通过上面两点来看的话这两个的属性应该是必传的,对于传递的值string和number
// 都可以
// 4.这样看的话选中状态的切换是因为点击改变了radio组件里面的label的值,然后
// 将改变后的label值传递到组件外部,将组件外部绑定的value值将其修改掉,因此
// 得到另外的value与label的相等关系,因而改变选中状态。
<el-radio v-model="radio" label="1">选项1</el-radio>
<script>
export default {
name: 'co-radio',
props: {
value: {
type: [String, Number],
},
label: {
required: true,
type: [String, Number],
},
},
methods: {
trigger() {
// 7.当点击6创建的事件的时候,会触发父组件中绑定的input事件,
// 在触发事件的同时将修改后的label的值传递出去,此时外部
// 绑定的value的值也会改变成为传递的label值,
this.$emit('input', this.label)
},
},
};
</script>
完整写法
<script>
export default {
name: 'co-radio',
props: {
value: {
type: [String, Number],
},
label: {
required: true,
type: [String, Number],
},
},
methods: {
trigger() {
this.$emit('input', this.label)
},
},
computed: {
isChecked() {
return this.value === this.label;
},
// 实现一个在文档上面的一个disabled的不可选的属性,如果不传值,
// 默认为false,如果传值,以传递的布尔值为准
isDisabled() {
// 以前的文章提到过,组件上面的属性如果没有声明的话都是attr属性,
// 那么就可以使用vm.$attrs.属性名来访问
const disabledValue = this.$attrs.hasOwnProperty('disabled');
if (disabledValue) {
if (typeof disabledValue === 'boolean') {
return disabledValue;
} else {
return true;
}
} else {
return false;
}
},
},
};
</script>
style
<style lang="scss">
.co-radio {
/* pointer-events: none; */
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;
.is-checked + &__label {
color: #409eff;
}
.is-checked > &__inner::after {
transform: translate(-50%, -50%) scale(1) !important;
}
&__input.is-checked > &__inner {
background-color: #409eff;
border-color: #409eff;
}
&__input {
white-space: nowrap;
cursor: pointer;
outline: none;
display: inline-block;
line-height: 1;
position: relative;
vertical-align: middle;
.co-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(1);
transition: transform 0.15s ease-in;
}
}
> input {
display: none;
}
}
&__label {
font-size: 14px;
padding-left: 10px;
}
}
</style>
扩展:单选框组的实现
实现目标
// 只需要在el-radio-group上面使用v-model绑定,就能将el-radio-group里面
// 所有的el-radio都使用v-model绑定,就不用一个一个的去添加了
<el-radio-group v-model="radio">
<el-radio :label="3">备选项</el-radio>
<el-radio :label="6">备选项</el-radio>
<el-radio :label="9">备选项</el-radio>
</el-radio-group>
简单实现el-radio-group组件
<template>
<div class="el-radio-group">
<slot />
</div>
</template>
<script>
export default {
// 给其一个name的属性
name: 'co-radio-group',
// 因为使用v-model进行了绑定,所以存在一个props的value属性
props: {
value: {
required: true,
type: [String, Number],
},
},
};
</script>
注意:在定义组件的时候,按照规定需要给其一个name的属性,如果是去找父组件的name属性,那么使用vm.$parent.$options.name可以获取,如果寻找子组件的name属性,使用vm.$children.$options.name可以获取,
el-radio作为el-radio-group的时候
// el-radio作为el-radio-group的时候,结构没有什么变化,不过在属性和功能上
// 存在一些差异,下面解释一下差异在什么地方
<script>
export default {
name: 'co-radio',
props: {
value: {
// 1.因为如果在el-radio-group上面使用v-model绑定,就能将
// el-radio-group里面所有的el-radio都使用v-model绑定,
// 而绑定就存在value的props属性,所以这里的value属性变得
// 不是必传属性了。
type: [String, Number],
},
label: {
required: true,
type: [String, Number],
},
},
methods: {
trigger() {
// 5.因为既可以在el-radio-group上使用v-model,也可以在自己身上使用,
// 那么就需要判断一下input事件在谁的身上,也就是需要往谁的身上传值了
this.$parent.$options.name === 'co-radio-group'
? this.$parent.$emit('input', this.label)
: this.$emit('input', this.label);
},
},
computed: {
// 2.现在因为value的来源有两个,所以需要进行判断,并且以_开头的属性是
// 不希望外界直接访问的。
_value() {
// 3.判断一下el-radio标签的父组件是不是el-radio-group,如果是,那就
// 说明此时是嵌套关系,value的值来自el-radio-group,如果不是,就
// 表示不是嵌套关系,那么v-model肯定绑定在自己的身上,所以value的
// 值自然就来自自己了。
return this.$parent.$options.name === 'co-radio-group'
? this.$parent.value
: this.value;
},
isChecked() {
// 4.同样还是判断value与label的值是否相等,只是有区别的在于value值的
// 来源不一样而已。
return this._value === this.label;
},
isDisabled() {
const disabledValue = this.$attrs.hasOwnProperty('disabled');
if (disabledValue) {
if (typeof disabledValue === 'boolean') {
return disabledValue;
} else {
return true;
}
} else {
return false;
}
},
},
};
</script>
选中的流程(我的理解)
:因为radio标签的label属性是必传的,所以我的每一个radio标签上面有一个不变的label属性,那么这个属性值也不会发生变化,如果默认选中标签的label的值是1,然后我点击一个label的值为2的标签,因为点击就会触发trigger函数,函数里面的$emit函数会向radio组件外部传递值,在$emit函数触发的过程中,我得标签的label的值由原来的1变成了现在的2,此时会触发radio组件外部的使用v-model在组件上面绑定的那个回调函数,因为使用v-model默认会存在一个input的事件,那么此时就会触发绑定的这个input事件,而事件对应的回调函数的参数就是我所传递过来的改变后的label的值,在input事件触发之后,它会使用改变后的label的值将绑定的value的值修改掉,而radio组件内部的value的属性与v-model绑定的value是相同的,因为radio组件内部的label的值是没有变化的,而绑定的value的属性发生改变,这样他就会寻找哪一个组件的label和我的value值是相等的,这样就完成了从label为1的组件切换到了label为2的组件了,后面的切换以此内推,
组件的全局注册—Vue.component的替代Vue.use()
概念:直接使用Vue.use(组件名)
就可以全局注册一个组件了
作用:使其可以辅助我们间接调用其他的Vue的静态方法
原理:
// 举例:为Vue添加一个静态方法$use
// 1.组件就是一个配置对象,所以typeof组件的时候,它会是一个object
// 2.然后它会给执行组件里面的一个install的方法,这个方法执行的时候
// 会自动传递一个Vue实例作为install函数的参数
// 3.在install的方法里面,使用Vue.component全局注册组件,组件的名字
// 是自己给组件的name的属性
Vue.$use() = function( model ) {
if( typeof model === 'object' && module.install === 'function') {
module.install( this )
}
}
function install( v ) {
v.component( this.name, this )
}
使用:对于自己定义的组件需要手动添加一个install的方法,这样才能让自己定义的组件使用use全局注册
// 在组件的文件夹里面新建一个index.js的文件书写以下文件:
import 组件名 from 组件存放的位置
组件名.install = function ( v ) {
v.component( this.name, this )
}
export default 组件名
原文链接:https://juejin.cn/post/7225133152490586171 作者:梦满枝头月初盈