谈谈 Vue 业务组件
春节的假期刚刚过去不久,大脑还没有从假期综合症中缓过来,就迎来了开工的日子,不知道各位有没有收到开工大红包?有没有被虐狗?
什么是组件
Web 页面上的每个独立的可视/可交互区域视为一个组件,组件就好像我们的 PC 组装机一样,整个机器(应用)由不同的部件组成,例如显示器、主板、内存、显卡、硬盘等等。页面就是由一个个类似这样的部分组成的,比如导航、列表、弹窗、级联、下拉菜单等。页面只不过是这些组件的容器,组件自由组合形成功能完整的界面,当不需要某个组件,或者想要替换某个组件时,可以随时进行替换和删除,而不影响整个应用的运行。
项目结构约定
- 每个 Vue 组件的代码建议不要超出 200 行,如果超出建议拆分组件
- 拆分文件目录,将页面、通用组件、工具、通用样式、路由等单独放在一个文件夹中。
- 统一 Vue 文件的书写格式参考 Vue 组件风格 或 风格指南(官方)。
例如我的项目结构:
src ├── assets # 图片、icon 等静态资源 ├── common # state、工具函数、公共常量 ├── components # 公共组件 ├── mixins # 公共 mixin 文件 ├── pages # 页面组件 ├── router # 路由 └── styles # 公共样式文件
业务组件设计
我们在项目开发中必然会使用到一些通用的组件,例如弹窗、级联、下拉菜单等。好在有很多 UI 组件库例如 element-ui、Mint UI、framework7,这些 UI 组件库已经帮我们实现了最基础的功能,我们只需要拿来用就可以了。然而现实开发中,产品总是会脑洞大开,提出这样或那样的需求,很显然这些 UI 库并不能实现所有的业务需求。所以这个时候我们就需要自己设计一个组件,或是在某个 UI 组件的基础上再进行一层封装。
封装组件
譬如下拉多选,element-ui 的 select 组件虽然可以实现这个功能,但是 select 组件多选会将选中项作为标签放在 input 中,当选项过多会导致标签换行,并不美观。所以我们需要自己封装一个 dropdown-list 组件,我们只需要将 element-ui 的 dropdown 组件做一个简单的封装就可以实现这个功能。
// dropdown-list.vue <template> <el-dropdown class="cpt-dropdown-list" trigger="click" :hide-on-click="false" @command="commandHandler"> <span class="dropdown-link">{{ text }}</span> <el-dropdown-menu slot="dropdown" class="cpt-dropdown-list-wrap"> <el-dropdown-item v-for="item in options" :key="item.key" :command="item" :class="{ active: isActive(item) }"> {{ item.label }} <i v-show="isActive(item)" class="el-icon-check"></i> </el-dropdown-item> </el-dropdown-menu> </el-dropdown> </template> <script> export default { props: { text: String, options: Array, selected: Array }, methods: { isActive (data) { return this.selected.some(item => item.key === data.key) }, commandHandler (data) { const temp = this.selected.slice() const index = temp.findIndex(item => item.key === data.key) if (index > -1) { temp.splice(index, 1) } else { temp.push(data) } this.$emit('update:selected', temp) } } } </script> <style lang="scss"> .cpt-dropdown-list { display: inline-block; .dropdown-link { color: #409eff; cursor: pointer; } } </style> // base.scss .cpt-dropdown-list-wrap { max-height: 300px; overflow: auto; .el-dropdown-menu__item { display: flex; justify-content: space-between; padding: 0 10px; &, &:hover { color: #606266; background-color: #fff; } .el-icon-check { margin-left: 10px; line-height: unset; } &.active { color: #409eff; } } }
样式作用域空间
使用 componet- 或 page- 作为前缀加上组件名称作为组件样式作用域空间。为什么要有样式作用域空间?因为随着项目不断的扩大,不同组件相同类名出现的可能性也越来越大,在某些场景下会导致样式冲突。譬如下面的例子:
// primary-button.vue <template functional> <div class="button"> blue </div> </template> <style lang="css"> .button { color: blue; } </style> // warning-button.vue <template functional> <div class="button"> red </div> </template> <style lang="css"> .button { color: red; } </style> //home.vue <template> <div class="page-home"> <button @click="isRed = !isRed">click me</button> <warning-button v-if="isRed"></warning-button> <primary-button v-else></primary-button> </div> </template> <script> import WarningButton from './warning-button.vue' import PrimaryButton from './primary-button.vue' export default { data () { return { isRed: true } }, components: { WarningButton, PrimaryButton } } </script>
像上面的例子,我们期望通过切换 isRed 的值来切换不同背景色的按钮组件,初始化的时候 warning-button 组件背景色是红色,连续点击两次按钮切换回来 ,这时 warning-button 组件的背景色却变成了蓝色。这是因为切换 isRed 的时候加载了 primary-button 组件的样式,primary-button 组件的样式在 warning-button 组件之后加载,导致 warning-button 组件的样式被覆盖了。上面的例子过于简单,但实际开发中是有可能遇到这种问题的,所以我们应该为组件添加样式作用域空间来避免这种情况。
第三方库
在实际开发中,我们会使用一些第三方的库,例如 Lodash、ECharts、高德地图 等。但产品可能提出一些第三方库本身不支持的功能,我们就需要在原有的基础上解析封装。例如我之前接手一个高德地图的项目,大部分页面都有高德地图功能,所以我在多个页面中共享一个地图实例。产品提出新需求,在不同页面中缩放的级别可能不同,而高德地图本身的 Zoom 组件不能实现该功能,所以我们需要自己封装这个功能。
<template> <div class="cpt-zoom"> <button @click="zoomIn">+</button> <span>{{ zoom }}</span> <button @click="zoomOut">-</button> </div> </template> <script> export default { props: { min: Number, max: Number, map: { type: Object, required: true } }, data() { return { zoom: this.map.getZoom() }; }, methods: { zoomIn() { // 处理 }, zoomOut() { // 处理 } } }; </script>
最后
在日常的业务组件的开发中,个人总结了几点:
- 这个功能是否通用(最少会被两个或以上页面引用)?如果功能只会在一个页面中使用,建议将该组件放在当前页面的文件夹中。
- 这个功能是否是独立,如果不是可以将这个组件拆封成多个组件,如果这些子组件没用被单独引用可以将它们放在同一个文件夹中。还有一种情况,如果这个组价代码过多超出 200 行,这时我们也应该将功能拆分出去,这样会方便阅读也便于维护。
- 为组件添加样式作用域空间,避免样式冲突。
- 只在业务需要该功能的时候添加,在开发中我时常会犯一个错误“以后可能会用到”。实际上我们认为这个功能可能会用上,所以过早的编写了该功能,最后发现它们只会静静的待在哪里,为组件添加了很多无用的代码。
- 如果组件本身没有 data,且没有复杂的计算属性,建议写成函数式组件。
上面提到的一些封装组件的建议,只是我个人在开发中做的一些总结,如果有什么不对的地方欢迎指出,最后祝大家 2018 开工大吉,狗年汪汪汪。
原文地址:https://zhuanlan.zhihu.com/p/33999571