Vue组件开发实践


软件编程界有一个面相对象的思想, 或者用另一句话就是为实例写模板, 初始化的时候调用模板(类)生成实例, 进行抽象化开发. 因此, 组件的开发和类的设计有着异曲同工之处.http://www.jianshu.com/p/52573a84f468

类的设计原则有以下几种, 分别是:

  1. 单一职责原则
  2. 接口隔离原则
  3. 开放封闭原则
  4. 依赖倒置原则

关于这方面的资料网上介绍比较多, 这里我对此概念进行迁移, 用于解释Vue组件开发中需要考虑的原则.

关于设计原则

1. 单一职责

这个比较好理解, 比如Alert/Toast/Actionsheet/Loading等组件, 通过名字就能知道他们都是与用户交互的弹出层, 用于提示用户操作结果的.

另外, 在Alert/Actionsheet/Loading中, 又有背景变黑的开关, 因此为了保证单一职责原则, 背景变黑这样的公共特性需要封装为独立的组件(Backdrop).

同样的例子在Input组件编写时也有体现. Input和Textarea组件两者有大部分的逻辑是共用的, 所以将共用的部分进行抽离放到mixin中.

所以, 我认为区分组件的原子性是根据组件是否共用为参考的. 此外, 不建议过度的原子性. 因此, 正确的时机因该是当代码有重复可合并的情况下进行抽离.

2. 接口隔离

Vue在设计组件的时候这方面就考虑的很周全. 目前(Vue2.x), Vue组件对外只有三个API:

  • Prps: 外部传递组件数据
  • Events: 组件向外发送事件(可传递数据)
  • Slots: 外部逻辑整合到组件中(插槽)

引入这张组件通讯图还是很必要的:


组件通讯]

这张图中并没有Slot, 是因为Slot中的内容组件是无法直接感知到. 关于组件使用的详细信息参考官网的组件开发教程.

因此, 如果你在使用组件, 在向组件内传递数据, 或者监听组件的状态使用PropsEvents就可以, 不建议通过this找$parent/$children等方法获取组件的直接操作. 另外, 如果是在编写开发组件, 那就随意了!

3. 开放封闭

拓展开放

这部分也是Vue在设计组件的时候内置的功能: MixinsExtends.

两个属性的功能类似, 简单的说就是将组件初始化的对象进行合并:

  • 对于属性(包括data/props/watch/methods/computed等): 数据会进行合并替换, 原始组件的优先级最高;

  • 对于钩子函数(created/mounted): Mixins/Extends中定义的钩子不影响原始组件的钩子, 但是会优先执行Mixins/Extends中的定义.

区别
  • 传参:

    • Mixins 需要传入数组
    • Extends 传入对象即可
  • 两者混用优先级:

    • 对于钩子函数: Extends > Mixins > Source
    • 对于属性: Source > Mixins > Extends

这部分也是单一原则的实现方式

修改封闭

正常情况下, 不会涉及到使用组件内部的方法, 组件对外全靠事件进行. 但是, 也有些情况在事件触发时传递组件的this, 让业务能够执行组件内部方法改变组件状态, 比如Refresher组件: 对外可调用内部两个方法:

  • complete(): 异步数据请求成功后, 调用这个方法; refresher将会关闭, 状态由refreshing -> completing.
  • cancel(): 取消 refresher, 其状态由refreshing -> cancelling

因此, 保证这部分不会在组件更迭发生变化也是很重要的!

3. 依赖倒置

这部分讲的是降低组件和业务之间的耦合度, 组件只要明确了使用调用的文档, 业务按照文档进行组件使用即可. 组件发生任何更新迭代优化等升级只要不改变定义的文档即可.

组件面相抽象开发, 不依赖具体实现. 组件开发就是为了降低耦合度而进行的.

写组件之前的建议

到这里我想到了"为人名服务"这句话, 也就是说开发组件前需要站在使用者角度考虑如何去使用这个组件. 所以我的做法如下:

  1. 先写DEMO实例, 将所有数据交互及操作交互等内容涵盖到里面
  2. 根据实例写文档, 规范API, 这部分可以和有经验的同时交流
  3. 准备实现具体逻辑

Vue组件的几种类型

这部分简单介绍Vimo框架中使用到的几种组件类型的实现思路

1. 弹出层组件

关于弹出层组件之前是参考mint-ui来写的, 但是vue和js文件杂糅的方式导致职责不清, 比如message-box组件, 关键部分代码流程如下:

// 1. 获取message-box.vue文件并extend
import msgboxVue from './message-box.vue';
var MessageBoxConstructor = Vue.extend(msgboxVue);

// 2. 根据MessageBoxConstructor生成实例instance, 使用div包裹
var initInstance = function() {
  instance = new MessageBoxConstructor({
    el: document.createElement('div')
  });
  instance.callback = defaultCallback;
};

// 3. 根据传入参数修改instance的属性, 然后挂到body上显示
var showNextMsg = function() {
  if (!instance) {
    initInstance();
  }
  if (!instance.value || instance.closeTimer) {
    if (msgQueue.length > 0) {
      currentMsg = msgQueue.shift();
      var options = currentMsg.options;
      for (var prop in options) {
        if (options.hasOwnProperty(prop)) {
          instance[prop] = options[prop];
        }
      }
      if (options.callback === undefined) {
        instance.callback = defaultCallback;
      }
      ['modal', 'showClose', 'closeOnClickModal', 'closeOnPressEscape'].forEach(prop => {
        if (instance[prop] === undefined) {
          instance[prop] = true;
        }
      });
      document.body.appendChild(instance.$el);
      Vue.nextTick(() => {
        instance.value = true;
      });
    }
  }
};

这么做不好的地方如下:

  1. vue并没有存在的意义, 直接写成html模板即可
  2. 组件没有初始化的生命周期过程, 即created/mounted等钩子都不起作用
  3. js部分包含了vue中的实现逻辑, vue只是作为了模板存在
  4. message-box.vue中定义的prop并没有发挥功能
  5. 使用setTimeout判断组件的开闭动画是否完毕, 正常应该监听transitionend事件

这部分也曾改写过很多次, 最终官网的一个APIpropsData开启了一个新思路.

  1. 先写普通组件一样先写弹出层组件,
  2. 之后用propsData传递数据
  3. el传递位置
  4. 组件的开闭有组件vue自己控制, 外部的js文件只是做以上步骤, 例如这样: Alert组件
  5. 组件具有完整的生命周期, 且props能够正常工作

关键代码如下:

var Comp = Vue.extend({
  props: ['msg'],
  template: '<div>{{ msg }}</div>'
})
var vm = new Comp({
  el: document.getElementById(position),
  propsData: {
    msg: 'hello'
  }
})
另外

关于监听组件动画结束返回Promise的解决办法:

  1. 使用transition的js钩子, 这里是说明.
  2. 开启关闭的函数返回Promise, 但是resolve方法在钩子中执行
// 1. 开启返回Promise
present () {
     const _this = this;
     _this.isActive = true;
     return new Promise((resolve) => {this.presentCallback = resolve})
},

// 2. transition中定义钩子
       <transition name="alert"
                    v-on:before-enter="_beforeEnter"
                    v-on:after-enter="_afterEnter"
                    v-on:before-leave="_beforeLeave"
                    v-on:after-leave="_afterLeave">
         .....
       </transition>

//3. 在钩子中执行presentCallback
_afterEnter (el) {
     this.enabled = true;
     // 执行开启的promise
     this.presentCallback(el);
},

2. 父子组合组件

这种组件组合方式类似于HTML中常用的<select><option>. 在Vimo中类似的组件有:Radio/Segment等, 大致的结构如下:

<ParentComponent v-model="value">
    <ChildrenComponent value="value1"> value1 </ChildrenComponent>
    <ChildrenComponent value="value2"> value2 </ChildrenComponent>
</ParentComponent>

前面说到, 组件使用需要遵循Vue定制的规范, 但是组件开发就没那么多限制, ParentComponentChildrenComponent是联动的关系, 因此需要定下他们之间的数据交互规则:

  1. 初始化时, ChildrenComponent组件将自己的this传递给ParentComponent, 父组件记录下来
  2. ChildrenComponent点击操作时, 调用ParentComponent组件的onChildrenChange函数, 传递自己的value
  3. ParentComponent组件得到value触发input更新v-modal值, 之后遍历子组件, 触发子组件的refresh方法, 传递value
  4. 子组件根据最新value更新自己的状态
  5. 以上过程设置相应的对外事件
  6. 父子组件组合需要设置assert, 如果assert失败需要给出使用提示, 比如"两个组件需要组合使用..."
  7. 子组件需要支持异步v-for填入,
  8. v-model默认值能正确反映到子组件

基本上按照以上的步骤就能搞定类似的组件啦, 不太明白的可以参考Radio组件的写法.

3. 其余

剩余的组件都比较好写了, 定好props和event就好, 比如:

  • 需要Slot部分
<Header>
     <Navbar>
          <Title>Alert</Title>
     </Navbar>
</Header>
  • 不需要Slot部分
<Toggle color="dark" v-model="displayData.dark" @onChange="onChangeHandler"></Toggle>

总结

写组件需要对Vue的API语法有深入的了解, 写起来才会的心入手. 此外, 对于组件我持有的态度是, 能从Github中找到且不难的组件都不自己写, 快速看看源码没致命错误就好. 如果是业务定制性的组件, 先写在业务中, 如果共用, 则提取出来(比较懒,

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值