You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
其实相对于 PC 端的组件库,移动端组件库有一个比较大的不同就是定制化要求较高。比如做 PC 端的 MIS 类的项目,如果使用 Vue 技术栈,大家往往会选择 element 或者是 iview,几乎都是拿来即用,最多换一下主题,很少会抠组件的细节,因为 MIS 类的项目是 to b 的,很多也是内部人员使用,所以对一些细节的要求并不高。而对于移动端项目,往往都是 to c 的,都有专门的 UI 设计,很少有完全符合要求的现成组件库能拿来用,所以 cube-ui 尽量提供一些通用性强的组件,并提供了自定义组件颜色的能力、和组件扩展能力,目的是让使用方 cube-ui 的基础上做二次开发,去满足自己的定制化需求。
Scroll 组件在其它地方都可以直接替换,另外除了有上拉加载和下拉刷新的场景,我们可以不给 Scroll 组件传 data 了,因为 1.5+ 版本的 better-scroll 已经有了根据 DOM 变化在合适时机自动 refresh 的能力了。
createAPI 的应用
前面我们简单地提到了 createAPI 的作用是把我们之前声明式的组件使用方式改变成 API 式的调用,为什么会有这样的需求呢?我们知道 Vue 推荐的就是声明式的组件使用方式,比如在使用一个组件 xxx,我们简单在使用的地方声明它就好了,就像这样:
<tempalte>
<xxx/>
</tempalte>
对于一般组件,这样使用并没有问题,但对于全屏类的弹窗组件,如果在一个层级嵌套很深的子组件中使用,仍然通过声明式的方式,很可能它的样式会受到父元素某些 CSS 的影响导致渲染不符合预期。这类组件最好的使用方式就是挂载到 body 下,但是我们如果是声明式地把这些组件挂载到最外层,对它们的控制也非常不灵活。其实最理想的方式是动态把这类组件挂载到 body 下,createAPI 就是干这个事情的。
先来看一下 createAPI 的文档,它可以把任何组件变成 API 式的调用。在我们的项目中有一个 Confirm 组件,它就是一个弹窗类型的组件。cube-ui 提供了所有弹窗类组件的基类组件 Popup,如果是新增一个弹窗类组件,推荐基于 Popup 做二次开发,不过我们的项目已经实现了全屏 Confirm 组件,目前需要实现的是调用它的使用可以动态挂载到 body 下,首先我们使用 createAPI 包装一下它:
背景
去年 6 月初,我在慕课网上线了一门 Vue.js 2.0 的高级实战课程音乐 WebApp 课程,教同学们如何去开发基础组件和业务组件。在一般大公司的实际项目中,并不会为每一个项目都去开发基础组件,他们往往会把基础组件收敛成一个组件库,供各个项目复用。滴滴也是如此,我们在去年初使用 Vue.js 去重构了我们的打车 WebApp,也抽象出了一套移动端组件库,在经过一年多的业务考验后,我们决定做开源,一方面是想把好的东西分享出去,并通过社区的反馈去完善我们的组件库;另一方面也是想让大家了解滴滴的前端,能吸引一些优秀的人才加入滴滴。于是在去年的 11 月份,我们团队开源了 cube-ui,到现在为止收到的反馈还算不错,也陆续有一些同学在生产环境也开始使用。
cube-ui 和其它同类型的开源组件库有一个很大的不同,它内部了使用了一个我们团队玩出来的“后编译”技术,它能帮我们玩出很多花样,比如减少组件包体积、支持 rem、支持自定义组件颜色等等,但带来好处的同时也会有一些不便(webpack 的配置会略显复杂),因此我们团队也为 cube-ui 在 vue-cli 的基础上扩展了一套脚手架,方便大家开箱即用。
其实相对于 PC 端的组件库,移动端组件库有一个比较大的不同就是定制化要求较高。比如做 PC 端的 MIS 类的项目,如果使用 Vue 技术栈,大家往往会选择 element 或者是 iview,几乎都是拿来即用,最多换一下主题,很少会抠组件的细节,因为 MIS 类的项目是 to b 的,很多也是内部人员使用,所以对一些细节的要求并不高。而对于移动端项目,往往都是 to c 的,都有专门的 UI 设计,很少有完全符合要求的现成组件库能拿来用,所以 cube-ui 尽量提供一些通用性强的组件,并提供了自定义组件颜色的能力、和组件扩展能力,目的是让使用方 cube-ui 的基础上做二次开发,去满足自己的定制化需求。
因为毕竟 cube-ui 是从滴滴的业务中抽象出来的,在做滴滴相关业务的时候,这些组件都能很好的满足需求,但是换成一个新的项目,cube-ui 好不好用呢,于是我想到了我的音乐课程项目,它有一些基础组件是可以从 cube-ui 里拿的,但是整体的配色风格和 cube-ui 的默认配色又完全不一样,正好可以来检验一波,接下来我分享一下 cube-ui 重构音乐课程项目的经验。
Webpack 配置修改
由于我们是现有项目,并不能使用脚手架去初始化项目,所以我们需要根据官网的文档去做 webpack 的相关配置。这里我要稍微提醒一些同学,在使用一个开源项目的时候,最好的方式就是阅读它的文档,遇到问题首先想的是查看它的 issue。那么 cube-ui 的文档在这里,我们来看一下快速上手部分。
安装 cube-ui
首先需要安装 cube-ui,这块很简单,直接运行命令就好了。
后编译配置
后编译简单的理解就是把编译工作交给应用来完成,也就是使用 cube-ui 的项目vue-music 来完成编译。由于是现成的项目,我们不能用脚手架初始化项目,那么所有的后编译相关的 webpack 配置都需要自己来动手,接下来我会一边教大家配置,一边来解释这些配置的作用。
修改 package.json 并安装依赖
首先需要修改的是 package.json 文件,我们需要在
devDependencies
添加几个插件,先简单对它们做一些介绍。babel-plugin-transform-modules
babel-plugin-transform-modules
是从babel-transform-imports
fork 来的,加上了对 style 的支持,为了解决组件按需引入的问题。stylus & stylus-loader
stylus
和stylus-loader
是为了编译 stylus 文件用的,因为 cube-ui 源码的 css 部分使用了 stylus 预处理器。webpack-post-compile-plugin
是为了解决后编译嵌套问题编写的 webpack 插件,因为在默认情况下,webpack 是不会编译node_modules
目录下的模块的,而我们的 cube-ui 是安装在node_modules
下的,为了编译它,需要在 webpack 配置文件中显示地声明include
指向node_modules
下的 cube-ui,例如:但这里会有一个问题,如果 cube-ui 一旦也后编译依赖其它模块,作为编译的应用方也需要把它们显示地写进
include
里,但这显然是不合理的,因为应用不应该知道 cube-ui 依赖的模块,每个模块只应该声明它自身的后编译依赖即可。那么webpack-post-compile-plugin
就是来解决这个问题的,它会读取每个模块 package.json 文件中声明的compileDependencies
,并递归去查找后编译依赖,然后添加到应用 webpack 配置的include
中,所以在我们应用项目中的 package.json 文件中,我们指定了compileDependencies
为[cube-ui]
。修改 .babelrc
这个配置项是为了配合
babel-plugin-transform-modules
使用的,给按需引入提供了一个语法糖。举个例子,当我们在代码中按需引入 cube-ui 的组件,如:相当于:
因为是引入源码,所以
import
的路径指向了src
目录,显然前者的写法比后者优雅了很多,并且一旦我们不用后编译,也不用去修改源码的import
方式,只需要修改 .babelrc 文件即可。修改 webpack.base.conf.js
这里就是对
webpack-post-compile-plugin
插件的应用,把它添加到plugins
中即可。修改 build/utils.js 中的
exports.cssLoaders
函数这里了一个 stylus 的配置项
'resovle url':true
,目的是为了解决被引入的 stylus 文件再去引入资源的相对路径的问题,参考官方文档。修改 vue-loader.conf.js
这里需要强制指定
css-loader
的选项extract
为 false,否则我们通过npm run build
编译后的项目异步加载 vue 组件会有问题。那么到这里,后编译的 webpack 配置就告一段落了,核心思想就是让我们的应用引入 cube-ui 的源码,并且接管 cube-ui 的编译工作。
Vue-music 源码修改
接下来就是修改我们项目的源码,我们会用到 cube-ui 的基础样式、
Scroll
滚动组件、Slide
轮播图组件、IndexList
索引列表组件以及createAPI
模块去把我们已有的Confirm
组件变成 API 式的调用。我们会在 main.js 里引用这些组件和模块:这里我们会
import Style
,它的作用是引入 cube-ui 提供的一些 reset 样式、基础样式和字体图标样式,那么对于我们的项目,就可以把 reset 样式移除了。对于组件的引用我们会使用
Vue.use
注册插件的方式,它内部会调用Vue.component
全局注册组件,这样我们就可以在任何组件内部里使用这些组件了。createAPI
是把我们之前声明式的组件使用方式改变成 API 式的调用,这块儿稍后我们会详细说明。IndexList 组件修改
音乐 App 的歌手页面有一个歌手列表,如下图所示:
![singer](https://camo.githubusercontent.com/717a996e9b0c49840415c77dd5a1f07eed353678ad081f0d67af1424cf95bc28/68747470733a2f2f7765626170702e646964697374617469632e636f6d2f7374617469632f7765626170702f6d757369632d6170702d73696e6765722e706e67)
它恰好可以使用 cube-ui 提供的
IndexList
组件,在我的教学课程中,我也是把它单独抽象出来的一个基础组件,所以替换就变的很容易了。学会使用一个组件,最好的方式就是看它的文档。cube-ui 提供的
IndexList
样式如下:可以看到相对于 cube-ui 的
IndexList
,我们的歌手页面的背景颜色、列表的样式都有所不同,幸好 cube-ui 支持自定义组件颜色和IndexList
的插槽功能,我们可以很好的解决这两个问题。IndexList
组件的颜色cube-ui 提供了自定义组件颜色的能力,我们打开它的文档,实际上只需要做两件事情。
首先在 src 目录下新建
theme.styl
文件,然后填入如下代码:这里我们用到了 stylus 的一个条件赋值的语法,它会先判断有没有对这个变量赋值,如果已经赋值了,则不会去覆盖这个变量的值。那么这里我们引入了 vue-music 项目中对于颜色定义的一些变量,把它赋值给了 cube-ui 关于
IndexList
组件所引用的一些颜色变量。接下来配置 webpack,修改
build/utils.js
里的exports.cssLoaders
函数中的stylusOptions
这里通过配置 stylus 选项,新增
import
配置项指向我们刚才创建的theme.styl
文件,可以达到的效果是在 stylus 的编译过程中,对每一个.styl
文件以及.vue
中的 stylus 部分都优先import
这个主题文件,这样就实现了组件颜色的自定义,会优先使用我们在theme.styl
文件中的颜色。IndexList
的插槽由于我们的列表项是图文混排的布局,和默认的样式不一样,因此我们需要用到插槽来自定义列表项布局,参考文档,我们对模板代码的修改如下:
我们使用 cube-ui 提供的
cube-index-list-group
和cube-index-list-item
做二重循环,因为是组件的循环,所以循环的过程中需要设置 key。这里有个地方需要注意一下,我们给IndexList
组件传的数据是 singers,而 singers 的数据结构是有要求的,它本身是一个数组,对于数组的每一项,它有组名name
和数据项items
。这个字段名和我们项目之前定义的略微不同,所以我们在处理从服务端拿到的歌手数据的时候,需要构造符合IndexList
约定的数据结构。最后还有一处细节的修改,我们项目中的每一组的标题样式和 cube-ui 的
IndexList
略微不同,可以通过覆盖 CSS 的方式对样式做修改。这里要注意的是,一旦我们要覆盖某个子组件的样式,那么引用该子组件的父组件(在我们这个 case 是
Singer
组件)样式部分就不能使用scoped
特性,因为如果设置了scoped
,Vue 在初始化的过程中会给组件的样式加上属性 id,那么就不能够覆盖 cube-ui 中的组件样式了。Slide 组件修改
音乐 App 的推荐页面用到了轮播图,如下图所示:
![slide](https://camo.githubusercontent.com/4854b529f84caee46befa1d436a2b6186962d66745c47ee133478c2a136238d2/68747470733a2f2f7765626170702e646964697374617469632e636f6d2f7374617469632f7765626170702f736869656c642f6d757369632d6170702d736c6964652e706e67)
在我们的项目中已经封装了轮播图组件,它恰好可以使用 cube-ui 的
Slide
组件无缝替换,同样的我们来看一下Slide
组件 的文档,修改代码如下:对于
Slide
组件内部的元素,我们用cube-slide-item
组件来做循环,由于底部的dots
样式很不一样,我们使用了作用域插槽,因为需要根据子组件的 current 来决定它渲染的active
样式;并且我们想让 dots 的位置向上偏移,所以我们依然采用覆盖 CSS 的方式:同样,我们也需要把
Recommend
组件 stylus 部分的scoped
移除。Scroll 组件修改
音乐 App 项目在 better-scroll 的基础上插件封装了 Scroll 组件,并在项目中大量应用,比如推荐页面、歌手详情页、搜索页面、歌曲列表、甚至是歌词列表。cube-ui 中也基于 better-scroll 封装了
Scroll
组件,它的功能更完善,所以我们决定替换Scroll
组件。Scroll
组件在项目中应用的地方非常多,这里我挑一个比较有代表性的场景,就是搜索页面的Suggest
组件,如下所图所示:Suggest
组件下方的列表是根据检索的关键词动态渲染的,它不仅可以局部滚动,还有一个上拉加载的功能,它就是移动端场景下分页功能的实现。我们完全可以用 cube-ui 的Scroll
组件来实现它,同样我们也是先去阅读它的文档,然后做如下代码的修改:这里需要注意两个地方,一个是
scrollOptions
,另一个是pullingUp
事件的回调函数searchMore
。scrollOptions
这个参数是 better-scroll 的 options 配置,由于我们使用了上拉加载的功能,所以需要配置
pullUpLoad
,这里我们指定了threshold
为 0,也就是刚到底部就触发pullingUp
事件,txt
设置为空因为在我们的项目中上拉加载不需要任何文案。searchMore
这个回调函数的作用就是根据条件去加载新的数据,如果没有更多数据了,我们直接调用
this.$refs.suggest.forceUpdate()
通知 Scroll 组件结束上拉的过程,另外单次加载数据发生任何异常的时候我们也都应该调用一次this.$refs.suggest.forceUpdate()
。Scroll
组件在其它地方都可以直接替换,另外除了有上拉加载和下拉刷新的场景,我们可以不给Scroll
组件传 data 了,因为 1.5+ 版本的 better-scroll 已经有了根据 DOM 变化在合适时机自动refresh
的能力了。createAPI
的应用前面我们简单地提到了
createAPI
的作用是把我们之前声明式的组件使用方式改变成 API 式的调用,为什么会有这样的需求呢?我们知道 Vue 推荐的就是声明式的组件使用方式,比如在使用一个组件 xxx,我们简单在使用的地方声明它就好了,就像这样:对于一般组件,这样使用并没有问题,但对于全屏类的弹窗组件,如果在一个层级嵌套很深的子组件中使用,仍然通过声明式的方式,很可能它的样式会受到父元素某些 CSS 的影响导致渲染不符合预期。这类组件最好的使用方式就是挂载到 body 下,但是我们如果是声明式地把这些组件挂载到最外层,对它们的控制也非常不灵活。其实最理想的方式是动态把这类组件挂载到 body 下,
createAPI
就是干这个事情的。先来看一下
createAPI
的文档,它可以把任何组件变成 API 式的调用。在我们的项目中有一个Confirm
组件,它就是一个弹窗类型的组件。cube-ui 提供了所有弹窗类组件的基类组件Popup
,如果是新增一个弹窗类组件,推荐基于Popup
做二次开发,不过我们的项目已经实现了全屏Confirm
组件,目前需要实现的是调用它的使用可以动态挂载到 body 下,首先我们使用createAPI
包装一下它:接着我们就可以在组件内部通过
this.$createConfirm
的方式调用它,我们在Search
组件中改变一下Confirm
组件的调用方式:当执行
.show
的时候,cube-ui 内部会把Confirm
组件动态挂载到 body 下。总结
到此这篇文章的主体内容就介绍完了,看似简单,但实际上我在重构的过程中还是发现了一些问题,顺便也对 cube-ui 和 better-scroll 做了一些优化。希望我的学生在看完这篇文章后能真正自己尝试着做一遍重构,因为很多细节的问题只有你去尝试做了才能发现,只有发现并解决问题你才能积累更多的经验;重构的过程中务必要看文档,遇到问题一定要自己先思考一遍,实在解决不了再求助。另外我也希望大家也多多使用 cube-ui,哪怕 cube-ui 能帮你解决一个小小的需求,那么我们觉得开源这件事情都是非常有意义的。如果大家在使用的过程中遇到一些问题,欢迎给我们提 issue & pr,帮助我们一起共建 cube-ui,也可以加 qq 群与我们交流,二维码如下:
如果 cube-ui 对你有帮助,也不要吝啬你的 star。
另附上 vue-music 项目的线上地址,扫下方二维码体验:
![music QR](https://camo.githubusercontent.com/b9aa4f1199681a3d5c3acb9c5631a5ba9e14849d6649e737c3419ae72342bcb0/68747470733a2f2f71722e6170692e636c692e696d2f71723f646174613d68747470253235334125323532462532353246757374626875616e6779692e636f6d25323532466d757369632532353246266c6576656c3d48267472616e73706172656e743d66616c7365266267636f6c6f723d25323366666666666626666f7265636f6c6f723d25323330303030303026626c6f636b706978656c3d3132266d617267696e626c6f636b3d31266c6f676f75726c3d2673697a653d323830266b69643d636c69696d266b65793d3733316262636332623439303435346432636336303466393835333939353263)
如果想跟着我学习这门 Vue.js 的进阶课程,真心想学到知识,请务必购买正版课程,你一定不会失望。
The text was updated successfully, but these errors were encountered: