👋 前言
- 用户就是上帝,站在上帝的角度也就是站在使用者的角度去看待组件。
- 用过不少优秀的
UI
库,用的时候美滋滋,轮到自己搭组件库的时候往往会去参考别人的源码。 - 看完源码后恍然大悟 噢!原来可以这样写,但心里难免会有疑惑别人是怎么想出来这种解决思路的?🤳
- 这一系列文章主要是面向未理解或者有疑惑的同学所以讲的比较基础,就让我们站在用户的角度去思考结构,看看换一种思路去写代码是不是有变化?
⌨️关于Input组件
👻为什么我们会用到Input
👨💼 作为用户
input
输入框是最常见的一种表单输入的控件,一般出现在问卷或者后台系统里面作为一个可以表达的组件。- 用户需要的输入框无非就是简洁好看的,如果搞的花里胡哨可能会有些不适应,特别是政务系统里面的表单更是需要同一色号。
👨💻 作为组件库使用者
- 我们可以看到很多的组件库都有
Input
,相信大家都很熟悉了。 - 当我们将组件库的
Input
组件放到我们的页面我们想要的效果是什么?
- 可以实现输入的功能
- 拥有原生
input
的所有功能 - 可以满足基本的双向绑定
- 可以在基本的需求上进行定制增加功能(比如:
禁用
添加图标
清空
)
- 在某一个方面来说,
Input
的出现也让我们在制作页面时更加方便统一样式和功能,节省不少时间
⚒️ 搭建组件
接下来可能用尽可能少的代码搭配
element
的源码进行结构说明,配合element Input源码食用更加美味喔
🔨 基本架子
- 要设计一个上图这种的
Input
不难,其实就是修改一下input
的样式罢了 - 总结起来总共也只有
4
个要点
- 准备一个外部的容器包含
input
- 根据不同类型呈现
text
textarea
等类型的输入框 input
可以使用原生的属性,例如(placeholder
)
<template> <div class="zl-input"> <template v-if="type !== 'textarea'"> <input ref="input" type="text" class="zl-input__inner" v-bind="$attrs"> </template> <template v-else> <textarea ref="textarea"></textarea> </template> </div> </template> ... <script> export default { props:{ value: [String, Number], type: { type: String, default: 'text' } } }; </script> ... 复制代码
- 以上就是
element
最简单的input
结构,可以看到子组件通过分辨type
来分辨是input
类型还是textarea
类型,这两种不同类型需要做不同处理,v-bind="$attrs"
可以将没有在props
出现的属性直接给到input
上就例如placeholder
可以直接作为input
的属性本文就主要讲一讲input
,两者其实没什么差别,关于具体样式可以看element 样式 - 当然这只是一个架子我们还需要加上双向绑定和其他功能。
🎶 双向绑定
- 单单只是一个架子可不行,我们需要记录下来输入的值,这时候就需要用到外面的
v-model
了 - 在父组件我们用
v-model
传入了一个值, 而v-model
的语法糖会把这个值当成props
的value
传到子组件,子组件只要通过$emit
时间改变外部的input
事件就可以啦。
<template> <div class="zl-input"> <template v-if="type !== 'textarea'"> <input ref="input" type="text" class="zl-input__inner" v-bind="$attrs" @input="handleInput"> </template> <template v-else> <textarea ref="textarea"></textarea> </template> </div> </template> ··· computed:{ nativeInputValue() { return this.value === null || this.value === undefined ? '' : String(this.value); } }, watch:{ nativeInputValue() { this.setNativeInputValue(); }, }, mounted () { this.setNativeInputValue(); }, methods:{ handleInput(event) { this.$emit('input', event.target.value); }, getInput() { return this.$refs.input || this.$refs.textarea; }, setNativeInputValue() { const input = this.getInput(); if (!input) return; if (input.value === this.nativeInputValue) return; input.value = this.nativeInputValue; }, } ··· 复制代码
- 在这里要注意的是我们没有像往常做双向绑定一样直接在
input
绑定:value="xxx"
而是通过手动赋值在setNativeInputValue
控制外部传进来的value
值,一开始看到element
的源码的时候我也很疑惑?为什么要这样做,直接使用:value
不是更方便吗,最后在一个issues找到了答案。
- 意思大致就是
element
的input
组件在输入前后会有一些处理,而这些处理跟:value
的模式冲突了,最后改成了手动赋值,如果我们自己写的组件没有这种bug
的话两种方式都可以使用,就当是学到了一种方法吧。 - 这样一来我们的一个最简单的
input
就做好了。
🧮 更多需求
- 一个最简单的架子搭好了接下来就可以定制我们的组件了。
- 比如说禁用啊,加图标啊,相信大家也已经很熟悉了,无非就是通过插槽获取通过
props
实现动态样式切换即可。 - 除了样式的切换还加上了一些前置内容和后置内容插槽来便利使用者,实现也不难就是在不同的的地方加上具名插槽即可。
- 在事件上也可以定制很多情况比如
focus
blur
这些都是在input
绑定事件即可,更多的实现可以参考传送门进行学习~
👋 写在最后
- 总的来说
Input
组件相对于其他复杂组件比较简单,只要把架子搭出来了剩下就是不断的优化加内容,大家也可以一起写写组件试试,难点在于合理制作双向绑定不跟其他事件冲突。 - 对于组件库的搭建我也在慢慢的摸索,讲的都是我自己得出来的分享所以说可能对于大佬来说会比较基础,但我相信我的不断输出可以帮助到一些有疑惑的同学。
- 如果您觉得这篇文章有帮助到您的的话不妨🍉关注+点赞+收藏+评论+转发🍉支持一下哟~~😛