从0开始写Vue组件(上)

好一段时间没写文章了。嗯,之前欠的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组件(下)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • 这篇笔记主要包含 Vue 2 不同于 Vue 1 或者特有的内容,还有我对于 Vue 1.0 印象不深的内容。关于...
    云之外阅读 5,046评论 0 29
  • Vue 实例 属性和方法 每个 Vue 实例都会代理其 data 对象里所有的属性:var data = { a:...
    云之外阅读 2,204评论 0 6
  • 下载安装搭建环境 可以选npm安装,或者简单下载一个开发版的vue.js文件 浏览器打开加载有vue的文档时,控制...
    冥冥2017阅读 6,033评论 0 42
  • Life Switch appliance。嗯嗯,又开始思考所谓的感悟问题,如果从生活的阶段来看,自己应该没长进多...
    YKCA阅读 191评论 0 0
  • 好的产品得做好两件事:特点和细节。特点用于将人们吸引到你的产品这来。细节 则是让用户保持使用这项产品的原因,正是细...
    粥一阅读 580评论 0 6