浅尝Vue.js组件(一)

组件名

一、使用 kebab-casevue

Vue.component('my-component-name', { /* ... */ })

二、使用 PascalCasewebpack

Vue.component('MyComponentName', { /* ... */ })

当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法均可以使用。也就是说 <my-component-name> 和 <MyComponentName> 都是可接受的。web

直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的

组件注册

全局注册

Vue.component('my-component-name', {
  // ... 选项 ...
})

注册以后能够用在任何新建立的Vue跟实例(new Vue)的模版中。正则表达式

基础组件的自动化全局注册

咱们每每有不少功能单一的基础组件,而这些组件有常常会被各个功能组件频繁的用到。这就会致使每一个功能组件都会有一长串基础组件的长列表。数组

若是使用了 webpack (或在内部使用了 webpack 的 Vue CLI 3+),则能够经过require.context来全局注册这些经常使用的基础组件。浏览器

import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'

const requireComponent = require.context(
  // 其组件目录的相对路径
  './components',
  // 是否查询其子目录
  false,
  // 匹配基础组件文件名的正则表达式
  /Base[A-Z]\w+\.(vue|js)$/
)

requireComponent.keys().forEach(fileName => {
  // 获取组件配置
  const componentConfig = requireComponent(fileName)

  // 获取组件的 PascalCase 命名
  const componentName = upperFirst(
    camelCase(
      // 剥去文件名开头的 `./` 和结尾的扩展名
      fileName.replace(/^\.\/(.*)\.\w+$/, '$1')
    )
  )

  // 全局注册组件
  Vue.component(
    componentName,
    // 若是这个组件选项是经过 `export default` 导出的,
    // 那么就会优先使用 `.default`,
    // 不然回退到使用模块的根。
    componentConfig.default || componentConfig
  )
})
全局注册的行为必须在根 Vue 实例 (经过 new Vue) 建立以前发生

局部注册

先经过一个普通的对象来定义组件,而后在须要使用的地方经过属性-值的方式来进行定义引入。app

var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }

new Vue({
  el: '#app',
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})

因为是局部注册,因此只在引入的模块内有效,若是但愿在子组件中使用,一样须要定义引入。函数

var ComponentA = { /* ... */ }

var ComponentB = {
  components: {
    'component-a': ComponentA
  },
  // ...
}

或者经过 Babel 和 webpack 使用 ES2015 模块

import ComponentA from './ComponentA.vue'

export default {
  components: {
    ComponentA
  },
  // ...
}

在模块系统中局部注册

在上面例子中经过ES2015的模块方式进行了组件的局部注册。咱们能够将全部的组件放到同一个目录下,每个都在一个单独的文件中,而后在须要使用的地方经过模块引入的方式进行局部注册便可。post

Prop

一、HTML 中的特性名是大小写不敏感的,因此浏览器会把全部大写字符解释为小写字符。因此在使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名须要使用其等价的 kebab-case (短横线分隔命名) 命名ui

Vue.component('blog-post', {
  // 在 JavaScript 中是 camelCase 的
  props: ['postTitle'],
  template: '<h3>{{ postTitle }}</h3>'
})

<!-- 在 HTML 中是 kebab-case 的 -->
<blog-post post-title="hello!"></blog-post>
使用字符串模版时则不存在上述限制

二、能够经过直接赋值的方式来给prop传递一个静态的值,也能够经过v-bind的方式来动态传入prop的值。

三、几种数据类型的传入方式

1.数字类型
<!-- 即使 `42` 是静态的,咱们仍然须要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:likes="42"></blog-post>

<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:likes="post.likes"></blog-post>

2.布尔值类型
<!-- 包含该 prop 没有值的状况在内,都意味着 `true`。-->
<blog-post is-published></blog-post>

<!-- 即使 `false` 是静态的,咱们仍然须要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:is-published="false"></blog-post>

<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:is-published="post.isPublished"></blog-post>

3.数组类型
<!-- 即使数组是静态的,咱们仍然须要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:comment-ids="[234, 266, 273]"></blog-post>

<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:comment-ids="post.commentIds"></blog-post>

4.对象类型
<!-- 即使对象是静态的,咱们仍然须要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post
  v-bind:author="{
    name: 'Veronica',
    company: 'Veridian Dynamics'
  }"
></blog-post>

<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:author="post.author"></blog-post>

5.对象的所有属性
post: {
  id: 1,
  title: 'My Journey with Vue'
}
<blog-post v-bind="post"></blog-post>

等价于

<blog-post
  v-bind:id="post.id"
  v-bind:title="post.title"
></blog-post>

单向数据流

经过prop传递数据是的父子组件之间造成了单向下行绑定,父级 prop 的更新会向下流动到子组件中,可是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而致使你的应用的数据流向难以理解。

每次父级组件发生更新时,子组件中全部的 prop 都将会刷新为最新的值。这意味着你不该该在一个子组件内部改变 prop。

两种在子组件中试图改变一个prop的状况:

一、这个 prop 用来传递一个初始值;这个子组件接下来但愿将其做为一个本地的 prop 数据来使用。在这种状况下,最好定义一个本地的 data 属性并将这个 prop 用做其初始值:

props: ['initialCounter'],
data: function () {
  return {
    counter: this.initialCounter
  }
}

二、这个 prop 以一种原始的值传入且须要进行转换。在这种状况下,最好使用这个 prop 的值来定义一个计算属性:

props: ['size'],
computed: {
  normalizedSize: function () {
    return this.size.trim().toLowerCase()
  }
}
注意在 JavaScript 中对象和数组是经过引用传入的,因此对于一个数组或对象类型的 prop 来讲,在子组件中改变这个对象或数组自己将会影响到父组件的状态。

Prop验证

咱们能够为Prop提供一个验证来保证传入的数据类型的有效性。为 props 中的值提供一个带有验证需求的对象,而不是一个字符串数组。

Vue.component('my-component', {
  props: {
    // 基础的类型检查 (`null` 和 `undefined` 会经过任何类型验证)
    propA: Number,
    // 多个可能的类型
    propB: [String, Number],
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
    // 带有默认值的数字
    propD: {
      type: Number,
      default: 100
    },
    // 带有默认值的对象
    propE: {
      type: Object,
      // 对象或数组默认值必须从一个工厂函数获取
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定义验证函数
    propF: {
      validator: function (value) {
        // 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }
})
注意那些 prop 会在一个组件实例建立以前进行验证,因此实例的属性 (如 data、computed 等) 在 default 或 validator 函数中是不可用的。

类型检查

type 能够是下列原生构造函数中的一个:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol

type 还能够是一个自定义的构造函数,而且经过 instanceof 来进行检查确认。

function Person (firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}

Vue.component('blog-post', {
  props: {
    author: Person
  }
})

非Prop特性

替换/合并已有的特性

对于绝大多数特性来讲,从外部提供给组件的值会替换掉组件内部设置好的值。classstyle 特性会将两边的值合并起来。

禁用特性继承

若是你不但愿组件的根元素继承特性,你能够在组件的选项中设置 inheritAttrs: false

注意 inheritAttrs: false 选项不会影响 styleclass 的绑定。

自定义事件

事件名

不一样于组件和 prop,事件名不存在任何自动化的大小写转换。而是触发的事件名须要彻底匹配监听这个事件所用的名称。不一样于组件和 prop,事件名不会被用做一个 JavaScript 变量名或属性名,因此就没有理由使用 camelCase 或 PascalCase 了。而且 v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (由于 HTML 是大小写不敏感的)。因此,始终使用 kebab-case 的事件名

自定义组件的v-model

能够经过model选项将v-model的value特性用于不一样的目的。

Vue.component('base-checkbox', {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    checked: Boolean
  },
  template: `
    <input
      type="checkbox"
      v-bind:checked="checked"
      v-on:change="$emit('change', $event.target.checked)"
    >
  `
})

<base-checkbox v-model="lovingVue"></base-checkbox>

这里的 lovingVue 的值将会传入这个名为 checked 的 prop。同时当 <base-checkbox> 触发一个 change 事件并附带一个新的值的时候,这个 lovingVue 的属性将会被更新。

将原生事件绑定到组件

可能有不少次想要在一个组件的根元素上直接监听一个原生事件。这时,你可使用 v-on 的 .native 修饰符来进行绑定。可是若是是一个相似原生元素的组件时,可能组件内部已经被重构了,此时会.native失效。

为了解决这个问题,Vue 提供了一个 $listeners 属性,它是一个对象,里面包含了做用在这个组件上的全部监听器。有了这个 $listeners 属性,你就能够配合 v-on="$listeners" 将全部的事件监听器指向这个组件的某个特定的子元素。

Vue.component('base-input', {
  inheritAttrs: false,
  props: ['label', 'value'],
  computed: {
    inputListeners: function () {
      var vm = this
      // `Object.assign` 将全部的对象合并为一个新对象
      return Object.assign({},
        // 咱们从父级添加全部的监听器
        this.$listeners,
        // 而后咱们添加自定义监听器,
        // 或覆写一些监听器的行为
        {
          // 这里确保组件配合 `v-model` 的工做
          input: function (event) {
            vm.$emit('input', event.target.value)
          }
        }
      )
    }
  },
  template: `
    <label>
      {{ label }}
      <input
        v-bind="$attrs"
        v-bind:value="value"
        v-on="inputListeners"
      >
    </label>
  `
})

.sync修饰符

在前面咱们知道,prop是单行向下的数据流方式,子组件不能直接修改prop的值,咱们推荐经过update:myPropName 的模式触发事件取而代之。

//父组件监听事件
<text-document
  v-bind:title="doc.title"
  v-on:update:title="doc.title = $event"
></text-document>

//子组件触发事件
this.$emit('update:title', newTitle)

咱们为这种模式提供一个缩写,即 .sync 修饰符:

<text-document v-bind:title.sync="doc.title"></text-document>
注意带有 .sync 修饰符的 v-bind 不能和表达式一块儿使用 (例如 v-bind:title.sync=”doc.title + ‘!’” 是无效的)。取而代之的是,你只能提供你想要绑定的属性名,相似 v-model。

当咱们用一个对象同时设置多个 prop 的时候,也能够将这个 .sync 修饰符和 v-bind 配合使用:

<text-document v-bind.sync="doc"></text-document>

这样会把 doc 对象中的每个属性 (如 title) 都做为一个独立的 prop 传进去,而后各自添加用于更新的 v-on 监听器。

将 v-bind.sync 用在一个字面量的对象上,例如 v-bind.sync=”{ title: doc.title }”,是没法正常工做的,由于在解析一个像这样的复杂表达式的时候,有不少边缘状况须要考虑。

小结

本次主要围绕组件注册和组件的Prop,以及组件的自定义事件相关内容进行了梳理。咱们知道,在Vue.js的开发过程当中,咱们会面临各类各样的组件相关的处理,组件也是咱们开发过程当中十分重要的部分,因此关于Vue.js组件相关的内容咱们分两次来进行整理。

相关文章
相关标签/搜索