淺嘗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組件相關的內容咱們分兩次來進行整理。

相關文章
相關標籤/搜索