很少有开发人员在上手编写 vue 组件时, 就考虑到将组件开源化的问题. 大部分开发者都是为了自己的项目编写组件, 在某些场景下决定通过编写组件的方式处理问题. 但另一方面, 由于大部分组件都是从某个特定场景开始逐步演化出来的, 因此许多组件在 Vue 生态系统中表现并不 "出色". 作者将通过本文为读者介绍构建优秀 Vue 组件的最佳实践.
从一方面来说, 这是种理想的方式, 它意味着 Vue 开发者能够使用越来越多的开源组件(在 npmjs.com 上搜索 "vue", 会查到 12000 多个包, 点击 https://www.npmjs.com/search?q=vue).
但另一方面, 由于大部分组件都是从某个特定场景开始逐步演化出来的, 而且并不是每个开发者都具备设计可跨平台重用的组件的经验, 因此许多组件在 Vue 生态系统中表现并不 "出色".
那么,"出色" 的定义是什么呢? 从较高层次来说, 这意味着 Vue 开发者在使用组件时感觉自然, 易于扩展, 并且能够与各种应用进行集成.
通过对大量的开源组件进行调查后, 我认为一个出色的 Vue 组件需要实现以下几点:
实现 v-model 兼容性
对事件透明
为恰当的元素赋予属性
拥抱浏览器标准, 实现键盘导航功能
优先选择事件, 而不是回调
限制组件内样式的使用
我自己是一名从事了多年开发的 web 前端老程序员, 目前辞职在做自己的 Web 前端私人定制课程, 今年年初我花了一个月整理了一份最适合 2019 年学习的 Web 前端学习干货, 各种框架都有整理, 送给每一位前端小伙伴, 想要获取的可以添加我的 Web 前端交流群 600610151, 即可免费获取.
实现 v-model 兼容性
某些组件主要是为表单字段所设计的, 包括搜索自动完成, 日期选择字段, 或是为简单的字段添加额外的功能, 使组件的使用者能够添加数据属性. 为了使所设计的组件符合使用标准, 最重要的一种方式就是支持 v-model.
根据 Vue 组件开发指南(链接: https://vuejs.org/v2/guide/components.html#Using-v-model-on-Components), 实现组件的 v-model 本质上只需传递一个 value 属性, 并提供一个 input 事件处理器.
举例来说, 假设开发者打算为输入框实现一个日期选择器的封装, 则需要通过 value 这个 prop 对日期选择器进行初始化, 并在选中状态下实现一个 input 事件, 以下面的代码为例:
对事件透明
为了支持 v-model, 组件必须实现 input 事件. 但其他事件, 例如单击, 键盘输入又该如何处理呢? 原生的事件会基于 HTML 元素进行冒泡, 而 Vue 的事件处理默认不会产生冒泡行为.
举例来说, 如果不做某些特殊处理, 以下代码是无法正常工作的:
showFocus 事件处理器在正常情况下不会被调用, 除非在封装组件的代码中暴露 focus 事件. 不过, Vue 为开发者提供了一种以编程方式访问某个组件的事件监听者的功能, 因此我们可以将监听方法赋予适当的对象, 即 $listeners 对象.
只需稍稍思考一下, 原因就会不言自明: 这种方式能够让开发者在组件中的合适位置传递事件监听器. 比方说, 在 textarea 封装组件中可以这样写:
这样一来, 在 textarea 中产生的事件就能够正常传递了.
为恰当的元素赋予属性
开发者应当如何处理属性呢? 例如 textarea 的 rows, 或是为任意的元素添加一个 title 属性以显示提示.
在默认情况下, Vue 会识别出添加在组件上的属性, 并将其应用在组件的根元素上. 这种行为在大部分情况下符合开发者的期望, 但也有例外. 如果我们再回顾一下上文所展示的 textarea 封装组件, 就会发现更自然的处理方式是将属性赋予 textare 本身, 而不是 div 元素.
为了实现这一点, 开发者需要告诉组件不要使用默认的方式添加属性, 而是通过 $attrs 对象直接为目标元素添加属性. 按以下方式编写 JavaScript:
然后在模板中这样写:
拥抱浏览器标准, 实现键盘导航功能
在 Web 开发过程中, 最容易被忽略的部分就是页面的可访问性和键盘导航功能. 如果你希望写出能够完美贴合 Web 生态的组件, 这也是最重要的一点.
本质上说, 这就意味着你需要确保组件符合浏览器标准: 可以使用 tab 键选择表单字段, 通常也可以使用回车键激活某个按钮或链接.
在 W3C 官网 (链接: https://www.w3.org/TR/wai-aria-practices/#aria_ex) 上可以找到为通用组件实现键盘导航功能的完整建议. 如果开发者能够遵循这些建议, 所构建的组件就能够适用于任何类型的应用程序, 尤其适用于那些关心可访问性的用户.
优先选择事件, 而不是回调
在组件与父组件进行数据通信以及用户交互时, 通常有两种选择: 在 props 中添加回调函数, 或是使用事件. 由于 Vue 的自定义事件不会像原生的浏览器事件一样产生冒泡, 因此这两种选择在功能上是一致的. 但是从组件重用性的角度来说, 我始终优先推荐事件而不是回调. 原因如下:
在 Fullstack Radio 的某期节目 (链接: http://www.fullstackradio.com/87) 中, Vue 的核心团队成员 Chris Fritz 给出了以下理由:
通过使用事件, 使父组件能够了解的信息变得非常明确. 它清晰地划分了 "从父组件中获取的信息" 和 "发送给父组件的信息" 这两种概念.
在某些简单的场景中, 可以选择在事件处理器中直接使用表达式, 以精简代码.
这种方式更符合标准习惯, Vue 的示例代码与文档都倾向于在组件与父组件进行通信时使用事件.
幸运的是, 如果某个组件已经选择了在 props 中定义回调的方式, 将其修改为暴露事件的方式也非常简单. 使用回调的组件代码看起来像这样:
然后以类似于这样的方式进行嵌入:
开发者可参照以下代码将其修改为基于事件的方式:
其父组件则对应进行修改:
限制组件内样式的使用
Vue 定义的单文件组件结构允许开发者在组件中直接嵌入样式, 尤其在结合了组件应用范围的情况下, 使开发者能够创建出完全封装的, 具备完整样式的组件. 并且这种组件不会影响到应用的其他部分.
由于这一系统的强大能力, 开发者会不自觉地选择将所有样式都封装在组件中, 构建出一个包含全部样式的组件. 问题在于, 没有任何应用的样式是完全相同的, 这种组件或许在你的应用中表现得非常美观, 但在其他应用中就显得一团糟. 而且组件的样式往往是在全局样式表之后才开始加载, 想要覆盖这些样式简直是一场恶梦.
为了避免这种情况的发生, 我的建议是, 如果某些 CSS 在结构上对于组件来说不是必需的(例如 color,border,shadow 等等), 则应当从组件文件中去除, 或是至少能够关闭这些样式. 可以选择提供一个能够自定义的 SCSS partial, 让使用者能够按照其意愿进行自定义.
不过, 如果仅仅提供一个 SCSS 文件, 这种方式仍然有一个缺陷. 组件的使用者不得不在样式表的编译过程中引入这个 SCSS, 否则就无法看到组件的样式. 为了克服这一缺点, 开发者可以通过一个 class 为样式提供控制范围. 如果 SCSS 使用了 mixin 的结构, 开发者就能够按照与使用者相同的方式利用这个 SCSS partial, 以实现更多的自定义样式.
随后在 JavaScript 中这样实现:
接下来:
通过这种方式, 组件自带的样式仍然按照开发者的想法呈现, 如果使用者需要对其进行自定义的修改, 也无需通过更高优先级的选择器进行覆盖. 只需将 disableStyles 这个 prop 设置为 true 即可关闭默认的样式, 随后选择按照自己的设置使用预定义的 mixin, 或是完全从头开始编写样式.
来源: http://www.jianshu.com/p/eeaea391ac83