从0开始写Vue组件(上)

作者: 谢mingmin | 来源:发表于2018-04-05 22:47 被阅读122次

    好一段时间没写文章了。嗯,之前欠的nodemcu的文章,我会找时间补上的。刚好项目组推新的固件,加入几个新的模块。放心,我一定会更新那个系列的文章的,只有有时间相信未来一段时间都没有的话。😅

    这回带来的是有关写Vue组件(.vue)的文章。主要会涉及的内容包括propsdatamethods等一系列概念。力争用最简单的语言让像我一样的萌新可以快速上手写出Vue组件来。跟着本来走一遍可以得到下面的效果。GitHub

    目标

    创建一个工程

    直接使用vue-cli创建一个webpack工程。虽然官方文档里面建议新手不要使用构建工具。不过,个人觉得用这个创建工程学习vue也蛮爽的。工具将很多工具打包到工程里面来,提供了一个相当不错的开发体验。


    vue-cli

    工具涉及到了几个组件需要注意一下,包括了vue路由,eslint,测试工具等。这些按需选择安装。大写字母表示是否默认安装。

    如果不想因为路由而困惑的话,可以不选择加入路由。但是,eslint这个对新手来说相当自虐的工具,我是强烈推荐安装的。eslint提供多到可怕的规范化检查,可以帮助避免写出bug才怪。配合vscode的eslint插件,可以得到更好的体验。

    创建完工程后可以得到类似于下图的工程结构

    工程结构
    重点关注src文件夹里面的内容。

    main.js里面new了一个vue实例,元素(el)指向了id=‘app’的div。app.vue这个文件又引用了pie.vue这个组件,而pie.vue又引用了mdiv.vue这个组件。看起来和洋葱一样,一层一层又一层。

    写第一个组件

    有了上面的了解后,可以动手写组件了。先从最里层开始写起。一个.vue文件,可以认为由三部分组成。

    <template>
      <div class="mdiv">
        <div class="head" @mousedown="mousedown">{{ title }}</div>
        <div class="menu">
          <div style="float: right; margin: 0 7px" @click="menuClick">
            <span class="fas fa-ellipsis-v" v-show="isedit"></span>
          </div>
          <div style="float: right; margin: 0 3px"  @click="expand">
            <span class="fas" v-bind:class="{ 'fa-expand': !isexpand, 'fa-compress': isexpand }"></span>
          </div>
          <slot name="contextmenu"></slot>
        </div>
        <slot name="container"></slot>
        <div class="resize" @mousedown="rmousedown"></div>
      </div>
    </template>
    
    <script>
    export default {
      data () {
        return {
          title: ''
        }
      }
    }
    </script>
    
    <style scoped>
    </style>
    

    第一个部分是template,就是HTML了。这部分的内容不多,直接贴代码。
    第二部分是script,就是组件的vue实例了。这个实例的el就指向了上面的template。
    第三部分是style,样式。加了scoped后,这个标签里面的样式就只在这个文件里面有效了。

    HTML

    在template里面有几个内容需要关注的。

    • @mousedown="mousedown" 这里的@是v-on的缩写,用来绑定事件。可以是DOM的原生事件,也可以是自定义事件。当mousedown事件发生时,会调用绑定的mousedown方法。

    • {{ title }}模板语法。当title发生变化的时候,页面元素也会跟着变化。

    • v-show="isedit" isedit是个布尔值,当值为正(true)的时候,这个标签就会显示出来,不然就隐藏起来。v-if也可以得到类似的效果,区别可以看这里v-if vs v-show

    • v-bind:class="{ 'fa-expand': !isexpand, 'fa-compress': isexpand }" 用来绑定class,实现切换class。isexpand也是布尔值,当值为真时,<span class="fas" v-bind:class="{ 'fa-expand': !isexpand, 'fa-compress': isexpand }"></span>将渲染成<span class="fas fa-compress"></span>

    • <slot name="contextmenu"></slot> slot是插槽,父组件可以将内容插到这个槽里面。这个是蛮好玩的特性。下篇文章还是说到。

    script

    这部分是vue的实例。实例有很多东西,有些是函数,有些事对象。

    • data 是个函数,返回一个对象。可以简单的将对象的属性看成组件的变量。
    data () {
        return {
          isexpand: false,
          // 空间位置相关
          pos: {
            left: this.outline[0],
            top: this.outline[1],
            height: this.outline[2],
            width: this.outline[3]
          },
          posNew: {
            left: this.outline[0],
            top: this.outline[1],
            height: this.outline[2],
            width: this.outline[3]
          },
          // DIV相关临时变量
          down: null,
          room: null
        }
      }
    
    • props 是一个对象,声明了组件接受父组件的传入的信息。!type是类型,如果写成字符串(‘string’)则会报错。更具体的看Prop 验证
      props: {
        name: {
          type: String, // 类型
          required: true // 是否必须
        },
        // [X, Y, W, H]
        outline: {
          type: Array,
          required: true
        },
        isedit: {
          type: Boolean,
          default: false // 默认值
        }
      }
    
    • mounted 是一个实例生命周期钩子,当el挂载后会调用这个函数。当然,还有其他生命周期钩子可以用。
      mounted () {
        this.$nextTick(() => {
          this.$el.style.left = this.outline[0] + 'px' // x
          this.$el.style.top = this.outline[1] + 'px' // y
          this.$el.style.width = this.outline[2] + 'px' // w
          this.$el.style.height = this.outline[3] + 'px' // h
        })
      }
    
    • computed 计算属性,一个相当好用的特性。title样子上像是个函数,在template被使用。当name发生变化后,title会重新调用。
      computed: {
        title () {
          return this.name
        }
      }
    
    • watch 侦听器,行为上和computed很像。但是,outline对应了属性。当outline发生变化时,会执行这个函数。
      watch: {
        outline () {
          this.outlineUpdate()
          this.pos = {
            left: this.outline[0],
            top: this.outline[1],
            height: this.outline[2],
            width: this.outline[3]
          }
          this.$emit('mdiv', 'resize')
        }
      }
    
    • emit 用于发送一个事件。第一个参数是事件名称。vm.$emit
    • methods 一些方法。可以在组件中使用。
    // 这个方法与一个div的click事件绑定,
    // 当事件发发生时,调用这个函数
    // 用来显示“全屏”效果
        expand () {
          this.isexpand = !this.isexpand
          if (this.isexpand) { // isexpand === true 变全屏
            this.$el.style.left = '1px'
            this.$el.style.top = '1px'
            this.$el.style.height = 'calc(100% - 2px)'
            this.$el.style.width = 'calc(100% - 2px)'
          } else {
            this.$el.style.left = this.pos.left + 'px'
            this.$el.style.top = this.pos.top + 'px'
            this.$el.style.width = this.pos.width + 'px'
            this.$el.style.height = this.pos.height + 'px'
          }
        }
    

    下面这些代码的主要功能时,鼠标可以通过右下角拖拽缩放div。

        // 缩放DIV
        rmousedown (down) {
          this.down = down
          this.room = this.$el.parentElement.getBoundingClientRect()
          this.room.width = this.room.width - this.pos.width - this.pos.left - 4
          this.room.height = this.room.height - this.pos.height - this.pos.top - 4
          console.log(this.room)
          document.onmouseup = this.rmouseup
          document.onmousemove = this.rmousemove
        },
        rmousemove (move) {
          let diff = {x: move.clientX - this.down.clientX, y: move.clientY - this.down.clientY}
          let x = diff.x < this.room.width ? diff.x : this.room.width
          let y = diff.y < this.room.height ? diff.y : this.room.height
          this.posNew.width = this.pos.width + x
          this.posNew.height = this.pos.height + y
          this.$el.style.width = this.posNew.width + 'px'
          this.$el.style.height = this.posNew.height + 'px'
        },
        rmouseup () {
          document.onmousemove = null
          document.onmouseup = null
          // 更新大小
          Object.assign(this.pos, this.posNew)
          this.$emit('mdiv', 'resize')
        }
    

    内容比较多,其他内容到GitHub上面看。

    style

    样式部分,没什么好说的了。

    第一个组件到这里就算实现了。然后就可以在其他组件里面使用,有点继承的意思。使用上类似这样。

    <template>
      <mdiv></mdiv>
    </template>
    

    当然,这样是用不了的,还差点东西。下一篇讲从0开始写Vue组件(下)

    相关文章

      网友评论

      本文标题:从0开始写Vue组件(上)

      本文链接:https://www.haomeiwen.com/subject/txydhftx.html