[关闭]
@lesonky 2018-05-26T02:30:16.000000Z 字数 3674 阅读 1389

如何写一个双向绑定的VUE组件

vue 学习分享


最近在重构公司项目代码,因为之前这一块不是我写的,所以也抽空看了一下之前同事的代码,可能是因为大家都是初学VUE,现在看来,有些代码还是略显稚嫩的,有一个小组件,虽然很简单,但是还是可以拿出来说一下的,因为它是一个双向绑定组件.

v-model是什么

教程上说的很清楚,v-model是一个语法糖,它是由 :value@input构成的[1]

为什么我需要v-model

使用v-model不仅仅是因为它简单,它的意义一方面是简化了书写,二是简化了逻辑,三是更加语义化,四是让API更加友好,简单易懂.
在VUE2.5之后,.sync修饰符又重新回归了,我们可以通过.sync修饰符来处理双向绑定问题,但是我觉得两者的使用场景是不一样的,v-model更多的是接收用户输入,而.sync更多的是同步数据.

怎么样实现一个可以使用v-model的组件

其实这个问题也很简单,只要仔细思考了上面两个问题,应该可以知道如何写,下面我就拿项目中的这个例子来做一个介绍,这个组件的效果是这样的,一个简单的switch开关:

QQ20180429-133843-HD.gif-66.8kB

改造之前的代码:

  1. <template>
  2. <div class="switch-outer"
  3. :class="{'on':on,'off':!on}"
  4. @click="handleChange">
  5. <span class="text"
  6. v-show="on">{{onText}}</span>
  7. <span class="text"
  8. v-show="!on">{{offText}}</span>
  9. <span class="active-ball"></span>
  10. </div>
  11. </template>
  12. <script>
  13. export default {
  14. data() {
  15. return {
  16. on: this.isOn
  17. };
  18. },
  19. props: {
  20. onText: {
  21. type: String,
  22. default: '开启'
  23. },
  24. offText: {
  25. type: String,
  26. default: '关闭'
  27. },
  28. isOn: {
  29. type: Boolean,
  30. default: true
  31. }
  32. },
  33. methods: {
  34. handleChange() {
  35. this.on = !this.on;
  36. this.$emit('change', this.on);
  37. }
  38. },
  39. watch: {
  40. isOn() {
  41. this.on = this.isOn;
  42. }
  43. }
  44. };
  45. </script>
  46. <style rel="stylesheet/scss" lang="scss" scoped>
  47. .switch-outer {
  48. position: absolute;
  49. cursor: pointer;
  50. color: #ffffff;
  51. .active-ball {
  52. width: 28px;
  53. height: 28px;
  54. border-radius: 50%;
  55. background-color: currentColor;
  56. position: absolute;
  57. top: 1px;
  58. }
  59. }
  60. .on {
  61. background: #00a4ee;
  62. .active-ball {
  63. left: 53px;
  64. transition: all 0.5s ease;
  65. }
  66. .text {
  67. margin-left: 14px;
  68. font-size: 14px;
  69. }
  70. }
  71. .off {
  72. background: #c0ccda;
  73. .active-ball {
  74. left: 2px;
  75. transition: all 0.5s ease;
  76. }
  77. .text {
  78. margin-left: 44px;
  79. font-size: 14px;
  80. }
  81. }
  82. </style>

改造之前的代码,并不能使用v-model来做双向绑定,但是很显然,这是一个switch开关,是一个用户输入组件,所以使用v-model是很自然的需求,但是目前这个组件的使用方式是这样的

  1.  <sky-switch class="share-switch"
  2. @change="handleChange"
  3. :isOn="group.is_files_share"
  4. ref="shareSwitch"></sky-switch>
  1. // 手动监听change事件,修改数据
  2. handleChange(on) {
  3. this.group.is_files_share = on;
  4. }

我们需要自己监听change事件,手动的修改值,这显然不符合我们对用户输入组件的认知,我们需要一个像那样的组件,通过 v-model 来绑定数据,剩下的交给vue来帮助我们处理.

显然这种使用方式并不友好,不能称为一个合格的组件.如果没有文档,我需要看源码才能知道这个组件如何使用,如果源码写的十分复杂,这个组件基本上是不可复用的.

接下来我们就开始魔改这个组件代码,删除不必要的监听和属性,简单直接的利用v-model来实现这个功能

改造后的代码

  1. <template>
  2. <div class="switch-outer"
  3. :class="{'on':on,'off':!on}"
  4. @click="handleChange">
  5. <span class="text"
  6. v-show="on">{{onText}}</span>
  7. <span class="text"
  8. v-show="!on">{{offText}}</span>
  9. <span class="active-ball"></span>
  10. </div>
  11. </template>
  12. <script>
  13. export default {
  14. props: {
  15. onText: {
  16. type: String,
  17. default: '开启'
  18. },
  19. offText: {
  20. type: String,
  21. default: '关闭'
  22. },
  23. value: { // 将 isOn改成 value ,用来接收 v-model 的传值
  24. type: Boolean,
  25. default: true
  26. }
  27. },
  28. methods: {
  29. handleChange() {
  30. this.on = !this.on; // 通过赋值操作来触发属性 on 的 set 方法
  31. this.$emit('change', this.on); // 发送 change 事件,作为一个事件钩子,方便用户处理自己的逻辑
  32. }
  33. },
  34. computed: {
  35. on: { // 去掉了data里面的on,使用计算属性
  36. get() { // get时返回 value值
  37. return this.value;
  38. },
  39. set(val) { // set时发送 input 事件
  40. this.$emit('input', val);
  41. }
  42. }
  43. }
  44. };
  45. </script>
  46. <style rel="stylesheet/scss" lang="scss" scoped>
  47. .switch-outer {
  48. position: absolute;
  49. cursor: pointer;
  50. color: #ffffff;
  51. .active-ball {
  52. width: 28px;
  53. height: 28px;
  54. border-radius: 50%;
  55. background-color: currentColor;
  56. position: absolute;
  57. top: 1px;
  58. transition: all 0.5s ease;
  59. }
  60. transition: all 0.5s ease;
  61. }
  62. .on {
  63. background: #00a4ee;
  64. .active-ball {
  65. left: 53px;
  66. transition: all 0.5s ease;
  67. }
  68. .text {
  69. margin-left: 14px;
  70. font-size: 14px;
  71. transition: all 0.5s ease;
  72. }
  73. }
  74. .off {
  75. background: #c0ccda;
  76. .active-ball {
  77. left: 2px;
  78. }
  79. .text {
  80. margin-left: 44px;
  81. font-size: 14px;
  82. transition: all 0.5s ease;
  83. }
  84. }
  85. </style>

这段代码的核心是那个计算属性on,巧妙的通过计算属性的getset方法,实现监听数据变化,get时返回通过props传入的value,set时,向父组件发送input事件,这样就可以配合v-model来使用了,因为v-model的本质就是向组件传一个valueprop,并且监听input方法[2]

style部分做了微调,因为这里加入了动画,但是原来的动画只有部分值是动的,会显得很突兀,所以调整为所有变化的属性都有动画,这样整个动画变得很平滑.

改造后的组件,使用方式是这样的:

  1. <sky-switch class="share-switch" v-model="group.share_file"></sky-switch>

简单明了,一看就是一个用户输入组件,和<input>元素一样.

当然,这个组件还不够完善,API接口也没有很好的设计,但是用来说明v-model这个问题应该是足够了.

写在最后

没事多重构自己的点,也可以重构别人的代码,看看别人是怎么实现的,自己又能怎么实现,相互对比,学习,这样才能快速进步.
前两天有事情,清了一天假,项目进度有点落后,今天过来补点进度,剩下点时间,写了这篇文字,虽然是一个很小的点,但是,希望能对对个别人有所启发.


[1] 这里只是简单的说,其实由什么属性和事件组成,可以通过一个model参数来定制
[2] 这里依然是从简单的角度来说.
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注