.cz-input-number{::v-deep.el-input__inner{文本对齐:左;}}我们在日常工作中经常会用到第三方的UI组件库,比如:element-ui、vant-ui、iview、ant-design等。无论是出于业务考虑或者单纯为了提高效率,我们会将一些经常使用的组件抽取出来封装成公共组件,这样我们就可以在不同的地方使用这个组件,减少重复的代码编写。我们把第三方组件库的封装称为组件的二次封装,那么这就给我们带来一个思考,我们重新封装的时候封装的是什么?重新包装时需要注意什么?在封装vue组件时,我们需要注意三个主要部分:prop、event、slot。prop:表示组件接收的参数,最好是对象的形式,这样可以为每个属性设置类型,默认值或者自定义校验属性的值,输入也可以通过type的方式校验、验证器等;event:子组件向父组件传递消息的重要方式;slot:可以动态的向组件中插入一些内容或组件,是实现高层组件的重要途径;当需要多个插槽时,可以使用命名插槽。你必须知道$attrs和$listeners当我们嵌套多级组件,需要传递数据时,通常的方式是通过vuex。如果只是传递数据,不做中间处理,用vuex来处理,就有点大材小用了。于是就有了$attrs/$listeners,它们通常是和inheritAttrs一起使用的。感觉挺晦涩的。简单来说,inheritAttrs:true继承除了props之外的所有属性;inheritAttrs:false只继承类属性。$attrs:包含属性绑定(除了class和style)在父范围内不被视为(也不期望是)道具,并且可以通过v-bind="$attrs"传递给内部组件。当组件未声明任何props时,它包括所有父范围绑定(类和样式除外)。$listeners:包含父作用域中的v-on事件监听器(不带.native修饰符)。它可以通过v-on="$listeners"传递给内部组件。它是一个包含所有作用于该组件的事件监听器的对象,相当于子组件继承了父组件的事件。监听器在重新打包组件时非常有用。如何使用$attrs和$listeners上面说了那么多,我们来看一个例子:在使用el-input-number时,当我们给它赋默认值null或者空字符串""时,0就会displayed,而这在我们的一些业务场景中是不太友好的,值是居中显示的,所以现在我们要做的改造是:值显示在左边,默认值是没有问题的displaying0,andthecontrolbuttonisnotdisplayeddefault:控件按钮默认不显示:控件设置为false显示在左边:通过样式控件默认值显示为0问题:计算时计算不是数字类型,赋值给undefined解决v-model是一个语法糖,可以拆解成props:value和events:input。也就是说,组件只需要提供一个名为value的prop和一个名为input的自定义事件。下面开始我们对el-input-number的封装:.cz-input-number{::v-deep.el-input__inner{文本对齐:左;}}上面@input="event)"的参数$event其实就是我们在输入框中输入的值,因为el-input-number里面的input元素触发了"input"自定义事件,不是原始输入事件;此时已经传出值,即event.target.value,所以可以直接在自定义组件的回调函数中接收到该值。有兴趣的朋友可以去element对应的源码看看。(准确的说是el-input的源码,因为el-input-number也是基于el-input的二次封装)。el-input-number二次封装后,我们的需求基本实现了,但是我们希望它的用法还是和el-input-number组件一样,这样即使别人使用我们封装好的组件,可以参考element对应的文档,正常使用。二次封装无论是为了业务还是为了方便,都应该尽可能的沿用原有基础的扩展,而不是为组件重新创建一套新的用法。毕竟包装的本质是为了提升用户体验,而不是增加更多不必要的精神负担。这里我们会用到上面介绍的$attrs和$listeners:$attrs"inherit"el-input-number原组件的所有v-bind属性$listeners"inherit"el-input-number所有原组件对于v-on事件,我们将v-bind="$attrs"和v-on="$listeners"添加到组件中:.cz-input-number{::v-deep.el-input__inner{文本对齐:左;}}为了方便使用,我将经常使用的组件封装为全局组件,所以直接按名称使用:效果:可以看到,当我们传入初始值null时,默认不会显示0。props中未定义的占位符通过$attrs透传也会生效。再试一次,change事件也能正常触发。el-input-number的二次封装就优雅完成了。一个想法,因为我们知道v-model是一种语法糖,与v-bind和v-on结合使用。那么我们是不是也可以使用$attrs和$listeners来为我们实现v-model呢?答案当然是肯定的。如果我们上面封装的cz-number-input不需要处理初始值,那么props中value的定义和@input="event)"的事件完全可以去掉。通过一层组件实现v-model我们再举个简单的例子。我们希望el-input可以默认清除,即clearable默认为true。在此基础上,我们做一个二次包如下:template>使用:效果:如你所见,当我们使用CzInput,v-model完全没问题。这里我们的父组件是CzInput,它贯穿了el-input这一层,v-model已经通过孙子的原始input实现了,不需要重新定义value属性和input事件。原因:由于$atrrs,父组件改变的数据会传递给子组件。后代组件发出一个输入事件,由于$listeners,该事件将被发送到父组件。并且由于父组件的v-model,新数据自动赋值给父组件的变量,所以实现了所谓的“双向绑定”。slotslot上面我们对el-input进行了简单的二次封装,封装后的组件继承了el-input的所有属性和事件,但是el-input也提供了一系列的slot,方便用户自定义,所以我们的封装也应该继承这些插槽。普通slotslotName是我们的槽名,默认的槽名是默认的或者可以不写。对于动态槽,如果要传输的槽不固定或者槽很多,我们可以通过动态槽名透传:这里我们给之前封装的CzInput添加一个槽:效果图:可以看到,使用slot是没有问题的。Scopeslots如果需要封装使用scopeslots的组件,我们可以通过以下方式实现:name="slotName"v-bind="slotProps"/>具体使用示例可以参考上篇文章中提到的vue项目开发。我遇到了这些问题。总结使用$attrs继承父组件的属性,使用$listeners继承父组件的事件。二次封装时,无论是为了业务,还是为了方便,槽位的转移都要在原有组件扩展的基础上。它不是一组新的组件用法。毕竟封装的本质是为了提升用户体验,而不是增加更多不必要的精神负担。