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. sass入门学习篇(一)

    先简单的介绍一下sass,如果你了解less,sass就没什么太大问题 Sass 是对 CSS 的扩展,让 CSS 语言更强大.优雅. 它允许你使用变量.嵌套规则. mixins.导入等众多功能, 并 ...

  2. Javascript一道面试题

    实现一个函数,运算结果可以满足如下预期结果: add(1)(2) // 3add(1, 2, 3)(10) // 16 add(1)(2)(3)(4)(5) // 15 function add () ...

  3. mysql查询今天、昨天、7天、近30天、本月、上一月 数据

    今天 select * from 表名 where to_days(时间字段名) = to_days(now()); 昨天 SELECT * FROM 表名 WHERE TO_DAYS( NOW( ) ...

  4. mui开发app之自定义事件以更新其他页内容

    我之前做过jquery mobile的开发,那还是前年的事情 在jquery mobile中,由于页面是存储在div[data-role=page]的dom中(jqmobile通过对data-role ...

  5. 忘记了root密码,如何进入系统?

    Issue 问题 忘记了root密码不能进入系统 如何进入系统? 环境 红帽企业版Linux所有版本 解决方法 可以进入单用户模式或者援救模式来改变root密码,如何进入单用户模式取决引导加载程序. ...

  6. Pangolin学习

    0.1. 资料 0.2. 使用说明 0.3. HelloPangolin 0.4. Plot data with ros 0.1. 资料 泡泡机器人 github example opengl中摄像机 ...

  7. [刷题]算法竞赛入门经典(第2版) 4-9/UVa1591 - Data Mining

    书上具体所有题目:http://pan.baidu.com/s/1hssH0KO 代码:(Accepted,0 ms) #include<iostream> unsigned N, A, ...

  8. 关于for循环的几种经典案例

    由于for循环可以通过控制循环变量的初始值和循环结束条件来改变遍历的区间,所以在排序或者遍历的时候,利用for循环就比较简单,以下是本人学习后得到的一些总结案例. 1.排序的应用 1)交换排序:通过取 ...

  9. Java学习笔记——设计模式之二.策略模式

    明确是王道 --Clean Code 先定义策略类 package cn.no2.strategy; public abstract class Strategy { //省略属性 //算法方法 pu ...

  10. Java学习笔记——浅谈数据结构与Java集合框架(第一篇、List)

    横看成岭侧成峰,远近高低各不同.不识庐山真面目,只缘身在此山中. --苏轼 这一块儿学的是云里雾里,咱们先从简单的入手.逐渐的拨开迷雾见太阳.本次先做List集合的三个实现类的学习笔记 List特点: ...