vue有着完整的组件化开发机制,但是官网只给了开发的方式,对于开发规范以及组件化开发的最佳实践,还需要我们来摸索。本文就平时开发中的经验来谈谈“把握边界”和“状态驱动”这两个话题。

边界把握

边界把握其实很好理解。在模块化编程中,我们通常要定义好一个模块的功能边界,做什么,不做什么,从外部接收什么,向外部提供什么。在vue的组件化系统之下,这些问题又更具体一些,需要我们细细把握。

划分业务逻辑

这个原则适用于任何模块化开发,一个组件要负责哪些业务,在开始写之初就应该非常明确,否则边界就容易模糊了。举个例子,页面上有个弹出层,里面会显示用户名。那么在弹出层组件中,需要有username这样一个数据吗?

很显然是不需要的。弹出层的任务就是:弹出、关闭、显示内容。至于是什么内容,组件并不需要关心。所以我们顶多会定义一个通用的content字段,或者干脆用slot。

组件简单了尚且容易把握,当业务较复杂的时候就需要好好斟酌了,这是个基本思维。

父子通信的注意点

这个话题想必大家不陌生,你甚至可以朗朗上口的背出来:父通过props传递数据给子,子通过emit发送消息给父。这有什么好说的呢?

props容易忽略的问题在于,当父组件传递一个对象给子组件时,这个传递就不再是“单向”的。因为子组件拿到的是一个引用,当子组件修改了该对象上的属性值,父组件的数据也会相应变化。数据流就变成了双向的,子组件是不应该直接修改父组件的数据的。所以我们要在props中只传递简单值。对象、数组这样的引用类型要避免传递。

为了保证props传递的数据类型,推荐在定义props的时候写明类型和默认值:

props: {
name: {
type: string,
default: ''
}
}

关于子组件emit消息,我之前也谈到过一个原则,子组件需要对外通知的是“我发生了什么”,而不是“你去干什么”。这只是语义上的一个差别,往小里说只是一个命名的事。但从逻辑上来讲,缺是一个边界把握不清楚的行为。

这也是很容易想通的,如果让子组件决定父组件的行为,那么他们在逻辑上便耦合了。举个例子:点击弹出层上的确定按钮,父组件去请求商品列表。那么子组件发出的消息应该叫"confirm"或"ok",而不是叫"request-product"。

避免全局操作

我们在平时的编程中,通常会用一些BOM的方法如history,或者是使用document上的方法,这类访问全局对象的行为,我也视之为“越界”行为。毕竟已经跨出了组件之外了。

一旦一个组件有操作全局对象的行为,那它就可以被认为有潜在威胁。所以通常应该注意以下方面:

  1. 用this.$el.querySelector代替document.querySelector,不要去查询组件外的DOM

  2. 用到的BOM接口,统一封装成模块,在组件中引入使用

  3. 本地存储也进行一次包装,例如,把localStorage相关操作统一封到一个storage.js模块中

  4. 子组件尽量避免监听window的事件,可让最外层组件监听,然后传递数据

vuex的状态管理

如果你使用了vuex,那么store中的数据管理也是需要留意的。vue完美集成了vuex这样一个全局状态管理工具,可以在任何组件中通过this.store访问/提交状态。

既然是全局状态,我们担心的又来了,组件内操作全局的东西,岂不是一次越界行为?而且各种commit散落在各个组件中,将来找起来岂不是很麻烦?

我的做法是这样的,单独定义一个模块,姑且叫做storeMonitor吧,所有修改全局状态的方法全部定义在这里面,组件借助这个storeMonitor去修改store中的数据,相当于是一个门面模式。这样的好处是,组件间接地去修改全局状态,相当于建立了一个隔离层。另一方面,所有的commit操作都集中在这个文件中,一目了然。

状态驱动

何为状态驱动

状态驱动也可以说是数据驱动,只不过数据是具体存在的(比如一个js对象),“状态”是抽象出来的一种描述。状态驱动就是指代码逻辑集中在数据操作, 而不是DOM操作以及样式操作。

举个例子,一个表单提交按钮,不可点击的时候要灰色背景,可点击的时候要蓝色背景。那么我们通过一个js变量disabled来控制,大致代码如下:

<button :class="disabled ? 'bg-gray' : 'bg-blue'">提交</button>

这不就是mvvm双向绑定的终极奥义嘛,说了半天废话。

其实上面的代码是有问题的。如果你隐隐觉得bg-gray、bg-blue这俩名字有点别扭,甚至那个disabled也看着不顺眼,那么你有可能要理解我想说什么了。

问题在哪里呢?想想这段代码表达了什么语义。“按钮不可用的时候给灰色背景,可用的时候给蓝色背景”,这,明明还是DOM世界的说法嘛。只是包上了双向绑定的皮而已,根本不是状态驱动。

而状态驱动的精髓,是要保留业务逻辑,消灭和DOM、样式有关的一切思维。而我们真正的业务逻辑可能是什么呢?“校验通过的时候让按钮可用,不通过的时候失效”。所以,正确的代码应该这么写:

<button :class="validate ? 'enable' : 'disabled'">提交</button>

什么?别骗我!你只是改了命名而已。

我没骗你,“命名即思维“,这是我一贯坚持的准则,胡乱给变量命名的人必然有一颗乱成麻团的脑袋。等你明白了舍生取义的道理,自然会回来和我一起念:「命名即思维」。

把页面上的所有功能都完整的抽象成状态,那就是状态驱动了,而这状态,不是样式的状态。那么,如何拥有正确的状态驱动思维呢?答案就是:面向对象。

面向对象的思维

不看表象,看抽象。前端所要有的面向对象思维差不多就是这样。

表象是啥呢?是输入框,是弹出层,是列表,是表格,是花里胡哨的各种颜色。

抽象是啥呢?是用户名,是密码,是登陆状态,是各种业务数据。我们把页面的内容抽象成对象的属性,把交互抽象成对象的方法。

还是举个例子吧,看下面这个丑陋的原型图:

那我们抽象出来的对象应该大致这样:

{
businessOptions: [],
currentIndex: 0,
selectedList: [],
select: function(index){ //选中操作 }
remove: function(index){ //删除操作 }
}

我们的代码逻辑应该是切换currentIndex,以及调用select方法来添加选项到selectedList数组。如果你想用active来表示当前激活的tab,或者是用left/right表示左边/右边两栏,那就大大的犯了表象主义错误。

在写小游戏的时候可能用到的面向对象思维较多,组件化开发中,也应当用这个思维去做整体设计。一个组件就是很具象的实体,所以要将之“物件化”。

css也要“状态”

css作为样式的描述语言,其命名方式以及组织方式有多种规则。在状态驱动的开发思维下,我倾向让css也具有“描述状态”的能力。看下面的一段sass代码:

.sidebar{
position: absolute;
bottom:;
width: 80%;
&.show{
display: block;
}
&.hidden{
display: none;
}
.btn{
display: inline-block;
width: 200px;
height: 20px;
}
&.open{
left:;
.btn{
background-image: url(left.png);
}
}
&.close{
left: -80%;
.btn{
background-image: url(right.png);
}
}
}

光看css,不看js代码的情况下,我们已经可以得知界面的展示逻辑了:有一个名为sidebar的侧边栏,它有四种状态,分别是:show、hidden、open、close。sidebar下有一个按钮btn,它在sidebar打开的时候是向左的背景图,在sidebar关闭的时候是向右的背景图。

这样一套结构清晰,语义明确的css规则,能够帮助我们很快理清页面逻辑,别人在看你的代码的时候一目了然。上面只是一个简单的例子,实践的时候会有复杂的场景,可根据具体功能划分出各自的作用域(嵌套语法),稍微需要花时间去设计,换来的是清晰的代码。

不需要动态创建组件

用mvvm框架去写弹框组件的时候,往往会有这样一个困惑:在jquery时代,我们通过 $.msg('内容')这样的方式调用弹框,此时在页面上动态创建一个节点,关闭弹框的时候再把节点移除。习惯于此,我们很希望能用同样的方式来处理弹框。

当然这在vue中也是可以做到的,方式就是动态创建标签,并且动态new一个组件实例去渲染它,在监听到close消息时,把这个节点手动删掉。大体代码如下:

const MessageConstructor = Vue.extend(alert);

const Message = (config) => {
instance = new MessageConstructor({
el: document.createElement('div')
});
document.body.appendChild(instance.$el); Vue.nextTick(()=>{
instance.show = true;
instance.content = config.content || '';
instance.type = config.type || 'danger';
instance.$on('close', function(){
this.show = false;
document.body.removeChild(this.$el);
});
instance.$on('confirm', config.onConfirm)
});
} export default Message;

这样的方式确实可以实现,但是其思想却是和状态驱动违背的,某个应用在某时某刻弹窗,这可以理解为这个应用的状态,我们只需用一个变量来标记该状态即可,犯不着手动创建节点、删除节点这么大动干戈。事实上vue作者也推崇这样来处理弹窗,节点始终挂载在页面,需要弹的时候给显示即可。

本篇结束,以上是笔者在实际开发者总结出的最佳实践,当然这只是一个开发模式,并无对错。大家可以参考,或引发其他思考。

聊聊vue组件开发的“边界把握”和“状态驱动”的更多相关文章

  1. vue前端开发那些事——vue组件开发

    vue的学习曲线不是很陡(相比其它框架,如anglarjs),官方文档比较全面,分为基础篇和高级篇.我们刚开始学习的时候,肯定像引用jquery那样,先把vue的js引进来,然后学习基础内容.如果仅仅 ...

  2. Vue组件开发实例(详细注释)

    Vue组件开发实例: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> &l ...

  3. 在vue组件中使用vuex的state状态对象的5种方式

    下面是store文件夹下的state.js和index.js内容 //state.js const state = { headerBgOpacity:0, loginStatus:0, count: ...

  4. Vue (三) --- Vue 组件开发

    ------------------------------------------------------------------好心情,会让你峰回路转. 5. 组件化开发 5.1 组件[compo ...

  5. vue 组件开发、vue自动化工具、axios使用与router的使用(3)

    一. 组件化开发 1.1 组件[component] 在网页中实现一个功能,需要使用html定义功能的内容结构,使用css声明功能的外观样式,还要使用js定义功能的特效,因此就产生了一个功能先关的代码 ...

  6. 三: vue组件开发及自动化工具vue-cli

    一: 组件化开发 1 组件 1: 组件(Component)是自定义封装的功能.在前端开发过程中,经常出现多个网页的功能是重复的,而且很多不同的网站之间,也存在同样的功能. 2: 什么是组件 而在网页 ...

  7. vue 开发系列(三) vue 组件开发

    概要 vue 的一个特点是进行组件开发,组件的优势是我们可以封装自己的控件,实现重用,比如我们在平台中封装了自己的附件控件,输入控件等. 组件的开发 在vue 中一个组件,就是一个独立的.vue 文件 ...

  8. Vue组件开发实践之scopedSlot的传递

    收录待用,修改转载已取得腾讯云授权 导语 现今的前端开发都讲究模块化组件化,即把公共的交互和功能封装到一个个的组件之中,在开发整体界面的时候就能像搭积木一样快速清晰高效.在使用Vue开发我们的vhtm ...

  9. Vue组件开发分享

    在开始本文之前,你可能需要先了解以下相关内容: Vue.js  一款高性能轻量化的MVVM框架 Webpack  前端模块化代码构建工具 Vue组件介绍 基于vue.js高效的双向数据绑定特性,让我们 ...

随机推荐

  1. 微坑---微信小程序ios上时间字符串转换为时间戳时,在开发工具上和安卓手机上运行成功

    给定一个时间字符串  var time="2017-02-27 16:42:53" js有三种转换为时间戳的方法:1.var timestamp = Date.parse(time ...

  2. vsftp使用方法与问题解决

    安装环境 OS:Centos 6.4 vsftp:vsftpd-2.2.2-11.el6_3.1.i686.rpm vsftpd配置文件:/etc/vsftpd/vsftpd.conf 一.      ...

  3. Github+yeoman+gulp-angular初始化搭建angularjs前端项目框架

    在上篇文章里面我们说到了Github账号的申请与配置 那么当你有了Github账号并创建了一个自己的Github项目之后,首要的当然是搭建自己的项目框架啦! 本人对自己的定位是web前端狗,常用开发框 ...

  4. cin 字符串输入

    cin 字符串输入 在学习c的时候,关于字符串的输入,记得有 scanf("%s",s); gets(s); 还有...o.o 好想没了... scanf("%s&quo ...

  5. futex-based pthread_cond

    pthread_cond的实现使用了几个futex来协同进行同步,以及如何来实现的. 假定你已经明白 futex,futex-requeue,以及 pthread lowlevellock. < ...

  6. java:Comparable比较器

    /*Comparable 是java.lang中的一个接口,所以是默认导入的,不需要显示的导入. *如果你先直接在本类中实现排序,那么可以直接实现该接口(例如:public class Compara ...

  7. 每个国家对应的语言Locale和国家代码对照表

    DNN3支持多语言,希望下面的语言代码与对应国家能对你有所帮助 语言代码 国家/ 地区 "" (空字符串) 无变化的文化 af 公用荷兰语 af-ZA 公用荷兰语 - 南非 sq ...

  8. JS中对于prototype的理解

    JS中的prototype是JS中比较难理解的一个部分 本文基于下面几个知识点: 1 原型法设计模式 在.Net中可以使用clone()来实现原型法 原型法的主要思想是,现在有1个类A,我想要创建一个 ...

  9. Tensorflow中的padding操作

    转载请注明出处:http://www.cnblogs.com/willnote/p/6746668.html 图示说明 用一个3x3的网格在一个28x28的图像上做切片并移动 移动到边缘上的时候,如果 ...

  10. 在Eclipse IDE使用Gradle构建应用程序

    文 by / 林本托 Tips 做一个终身学习的人. 1. 下载和配置Gradle Gradle Inc.是Gradle框架开发的公司,为Eclipse IDE提供了Gradle工具的支持. 此工具可 ...