前言👋
- 用户就是上帝,站在上帝的角度也就是站在使用者的角度去看待组件。
- 用过不少优秀的UI库,用的时候美滋滋,轮到自己搭组件库的时候往往会去参考别人的源码。
- 看完源码后恍然大悟 噢!原来可以这样写,但心里难免会有疑惑别人是怎么想出来这种解决思路的?🤳
- 这一系列文章主要是面向未理解或者有疑惑的同学所以讲的比较基础,就让我们站在用户的角度去思考结构,看看换一种思路去写代码是不是有变化?
关于Radio组件💿
为什么我们会用到radio
作为用户👨💼
- 在填写表单的时候,作为一个系统的使用者,我们下意识的习惯靠简单的鼠标点击或者手指触碰就可以选择你的答案而不是还要在键盘敲打文字。
- 为什么?还不是为了方便,相信没人会愿意用十分钟来填写全是input输入框的调查问卷吧?这也是为什么基本上所有调查问卷都是单选框和多选框的原因。
- 说白了就是便携方便点一下即可。
作为组件库使用者👨💻
- 当我们将组件库的radio组件放到我们的页面我们想要的效果是什么?
- 不花里胡哨
- 可以满足基本的选中需求
- 可以在基本的需求上进行定制增加功能(比如:
禁用
大小
图标
)
- 说白了就是我能写尽量少的代码来配置。
搭建组件⚒️
接下来可能用尽可能少的代码搭配
element
的源码进行结构说明,配合element Radio源码食用更加美味喔
基本架子🔨
- 如果想要好看的
radio
或者说是自定义的radio
组件那必不可能使用原生的radio
直接放到页面上,所以要将整体架子进行改造。 - 一个基本的
radio
架子主要有两个点,一个是原生的input
用来接收被点击事件,另一个就是制造给用户看的一个圆圈和一个文字展示区,大概是这样子的。
<template> <label class="zl-radio"> <span class="zl-radio__input"> <span class="zl-radio__inner"></span> <input ref="radio" type="radio" class="zl-radio__original" > </span> <span> <slot></slot> </span> </label> </template> 复制代码
- 可以看到在上面的代码中用
label
将所有包住是为了让用户不管选中按钮还是文字都可以选中,而label
也巧妙地运用了隐式关联 - 隐式的方式是把需要绑定的标签放到
label
内部,让label
包裹上这个需要绑定的元素就相当于点击了radio
- 上面那块
span
是表示前面的圆圈样式,inner
类则是中间那个实心的样式,original
类将input
用opacity: 0;
进行隐藏处理 - 下面的
span
大家应该也很熟悉就是一个插槽来接受外部的文字
双向绑定🧽
- 对于组件库使用者来说我们希望通过传入一个
v-model
和label
来控制我们表单的属性
- 为了接收
label
,架子很简单只需要用props
来接收即可,那v-model
传入的值呢?我们都知道v-model
其实也是语法糖,上面的代码也相当于
<l-radio label="第一个按钮" v-bind:value="radio" v-on:input="radio=$event"></l-radio> <l-radio label="第二个按钮" v-bind:value="radio" v-on:input="radio=$event"></l-radio> 复制代码
- 那么我们需要在子组件中
props
增加一个value
来接收传入的值,而在子组件中可以使用this.$emit('input', val)
触发父组件上的input
事件来改变选中
<script> export default { props: { value: {}, label:{}, }, computed:{ model: { get() { return this.value; }, set(val) { this.$emit('input', val); } }, } }; </script> 复制代码
- 在
props
接收传入的v-model
的数值和label
的数值,因为我们需要在子组件调用父组件的input
事件,所以我们可以写一个计算属性分别用get
接收父组件通过双向绑定传下来的value
,通过set
设置新的值,对应的template
如下
<template> <label class="zl-radio"> <span class="zl-radio__input" :class="{ 'is-checked': model === label }" > <span class="zl-radio__inner"></span> <input ref="radio" v-model="model" :value="label" type="radio" class="zl-radio__original" > </span> <span> <slot></slot> <template v-if="!$slots.default">{{ label }}</template> </span> </label> </template> 复制代码
- 可以看到第一个
span
在样式上使用动态class
如果使用者设置的label
与传入的value
相同时(即选中)则会有is-checked
的选中样式 - 在
input
标签里面也使用了v-model
对上述的计算属性model
来做一个双向绑定,这样的话我们子组件的input
的值就跟外部的表单属性进行连接起来了也就实现了父组件与子组件的双向绑定 - 因为有时候这个单选按钮的值就是那个文字的值所以在插槽下面加了一个
template
用于判断插槽中是否有文字时label
的展现
由于本次只是分析结构,关于样式相关可以到这里查看传送门
更多属性需求🏁
- 至此一个最简单
Radio
组件就完成了,实现了双向绑定 - 但我们往往还需要一些定制比如说禁用按钮的实现,我们希望通过在组件上添加属性来实现
- 对此我们要在子组件做的处理也是一样的
props
接收参数然后如果为true
做些什么处理如果false
做些什么处理
... <span class="zl-radio__input" :class="{ 'is-checked': model === label, 'is-disabled': disabled }" > <span class="zl-radio__inner"></span> <input ref="radio" v-model="model" :value="label" type="radio" class="zl-radio__original" :disabled="disabled" > </span> ... 复制代码
- 就拿禁用来说我们拿到了属性就跟上面的
is-checked
一样做动态样式绑定禁用的样式再让子组件input
的disabled
同步处理 - 一种属性是这样其他属性也是一样的道理就像
Element
也定义了很多的动态类来做匹配包括边框
、尺寸
等等
写在最后👋
- 对于组件库的搭建我也在慢慢的摸索,讲的都是我自己得出来的分享所以说可能对于大佬来说会比较基础,但我相信我的不断输出可以帮助到一些有疑惑的同学。
- 如果您觉得这篇文章有帮助到您的的话不妨🍉关注+点赞+收藏+评论+转发🍉支持一下哟~~😛