【Vue】組件的基礎與組件間通訊

Vue.js 最核心的功能就是組件(Component),從組件的構建、註冊到組件間通訊,Vue 2.x 提供了更多方式,讓咱們更靈活地使用組件來實現不一樣需求。

1、構建組件

1.1 組件基礎

一個組件由 template、data、computed、methods等選項組成。須要注意:前端

  • template 的 DOM 結構必須有根元素
  • data 必須是函數,數據經過 return 返回出去
// 示例:定義一個組件 MyComponent
var MyComponent = {{
  data: function () {
    return {
      // 數據
    }
  },
  template: '<div>組件內容</div>'
}

因爲 HTML 特性不區分大小寫, 在使用kebab-case(小寫短橫線分隔命名) 定義組件時,引用也須要使用這個格式如 <my-component>來使用;在使用PascalCase(駝峯式命名) 定義組件時<my-component><MyComponent>這兩種格式均可以引用。vue

1.2 單文件組件.vue

若是項目中使用打包編譯工具 webpack,那引入 vue-loader 就可使用 .vue後綴文件構建組件。
一個.vue單文件組件 (SFC) 示例:webpack

// MyComponent.vue 文件
<template>
    <div>組件內容</div>
</template>

<script>
  export default {
      data () {
        return {
          // 數據
        }
      }
  }
</script>

<style scoped>
    div{
        color: red
    }
</style>

.vue文件使組件結構變得清晰,使用.vue還須要安裝 vue-style-loader 等加載器並配置 webpack.config.js 來支持對 .vue 文件及 ES6 語法的解析。web

進一步學習可參考文章: 詳解 SFC 與 vue-loader

2、註冊組件

2.1 手動註冊

組件定義完後,還須要註冊纔可使用,註冊分爲全局和局部註冊:正則表達式

// 全局註冊,任何 Vue 實例均可引用
Vue.component('my-component', MyComponent)

// 局部註冊,在註冊實例的做用域下有效
var MyComponent = { /* ... */ }
new Vue({
    components: {
        'my-component': MyComponent
    }
})

// 局部註冊,使用模塊系統,組件定義在統一文件夾中
import MyComponent from './MyComponent.vue'

export default {
  components: {
    MyComponent // ES6 語法,至關於 MyComponent: MyComponent
  }
}

注意全局註冊的行爲必須在根 Vue 實例 (經過 new Vue) 建立以前發生。vue-router

2.2 自動註冊

對於通用模塊使用枚舉的註冊方式代碼會很是不方便,推薦使用自動化的全局註冊。若是項目使用 webpack,就可使用其中的require.context一次性引入組件文件夾下全部的組件:segmentfault

import Vue from 'vue'
import upperFirst from 'lodash/upperFirst' // 使用 lodash 進行字符串處理
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,
    componentConfig.default || componentConfig
  )
})

3、組件通訊

3.1 父單向子的 props

Vue 2.x 之後父組件用props向子組件傳遞數據,這種傳遞是單向/正向的,反之不能。這種設計是爲了不子組件無心間修改父組件的狀態。數組

子組件須要選項props聲明從父組件接收的數據,props能夠是字符串數組對象,一個 .vue 單文件組件示例以下函數

// ChildComponent.vue
<template>
    <div>
      <b>子組件:</b>{{message}}
    </div>
</template>

<script>
  export default {
    name: "ChildComponent",
    props: ['message']
  }
</script>

父組件可直接傳單個數據值,也能夠可使用指令v-bind動態綁定數據:工具

// parentComponent.vue
<template>
    <div>
      <h1>父組件</h1>
      <ChildComponent message="父組件向子組件傳遞的非動態值"></ChildComponent>
      <input type="text" v-model="parentMassage"/>
      <ChildComponent :message="parentMassage"></ChildComponent>
    </div>
</template>

<script>
  import ChildComponent from '@/components/ChildComponent'
  export default {
    components: {
      ChildComponent
    },
    data () {
      return {
        parentMassage: ''
      }
    }
  }
</script>

配置路由後運行效果以下:
clipboard.png

3.2 子向父的 $emit

當子組件向父組件傳遞數據時,就要用到自定義事件。子組件中使用 $emit()觸發自定義事件,父組件使用$on()監聽,相似觀察者模式。

子組件$emit()使用示例以下:

// ChildComponent.vue
<template>
  <div>
    <b>子組件:</b><button @click="handleIncrease">傳遞數值給父組件</button>
  </div>
</template>

<script>
  export default {
    name: "ChildComponent",
    methods: {
      handleIncrease () {
        this.$emit('increase',5)
      }
    }
  }
</script>

父組件監聽自定義事件 increase,並作出響應的示例:

// parentComponent.vue
<template>
    <div>
      <h1>父組件</h1>
      <p>數值:{{total}}</p>
      <ChildComponent @increase="getTotal"></ChildComponent>
    </div>
</template>

<script>
  import ChildComponent from '@/components/ChildComponent'
  export default {
    components: {
      ChildComponent
    },
    data () {
      return {
        total: 0
      }
    },
    methods: {
      getTotal (count) {
        this.total = count
      }
    }
  }
</script>

訪問 parentComponent.vue 頁面,點擊按鈕後子組件將數值傳遞給父組件:
clipboard.png

3.3 子孫的鏈與索引

組件的關係有不少時跨級的,這些組件的調用造成多個父鏈與子鏈。父組件能夠經過this.$children訪問它全部的子組件,可無限遞歸向下訪問至最內層的組件,同理子組件能夠經過this.$parent訪問父組件,可無限遞歸向上訪問直到根實例。

如下是子組件經過父鏈傳值的部分示例代碼:

// parentComponent.vue
<template>
    <div>
      <p>{{message}}</p>
      <ChildComponent></ChildComponent>
    </div>
</template>


// ChildComponent.vue
<template>
  <div>
    <b>子組件:</b><button @click="handleChange">經過父鏈直接修改數據</button>
  </div>
</template>

<script>
  export default {
    name: "ChildComponent",
    methods: {
      handleChange () {
        this.$parent.message = '來自 ChildComponent 的內容'
      }
    }
  }
</script>

顯然點擊父組件頁面的按鈕後會收到子組件傳過來的 message。

在業務中應儘可能避免使用父鏈或子鏈,由於這種數據依賴會使父子組件緊耦合,一個組件可能被其餘組件任意修改顯然是很差的,因此組件父子通訊經常使用 props$emit

3.4 中央事件總線 Bus

子孫的鏈式通訊顯然會使得組件緊耦合,同時兄弟組件間的通訊該如何實現呢?這裏介紹中央事件總線的方式,實際上就是用一個vue實例(Bus)做爲媒介,須要通訊的組件都引入 Bus,以後經過分別觸發和監聽 Bus 事件,進而實現組件之間的通訊和參數傳遞。

首先建 Vue 實例做爲總線:

// Bus.js
import Vue from 'vue'
export default new Vue;

須要通訊的組件都引入 Bus.js,使用 $emit發送信息:

// ComponentA.vue
<template>
  <div>
    <b>組件A:</b><button @click="handleBus">傳遞數值給須要的組件</button>
  </div>
</template>

<script>
  import Bus from './bus.js' 
  export default {
    methods: {
      handleBus () {
        Bus.$emit('someBusMessage','來自ComponentA的數據')
      }
    }
  }
</script>

須要組件A信息的就使用$on監聽:

// ComponentB.vue
<template>
  <div>
    <b>組件B:</b><button @click="handleBus">接收組件A的信息</button>
    <p>{{message}}</p>
  </div>
</template>

<script>
  import Bus from './bus.js' 
  export default {
    data() {
      return {
        message: ''
      }
    },
    created () {
      let that = this // 保存當前對象的做用域this
      Bus.$on('someBusMessage',function (data) {
        that.message = data
      })
    },
    beforeDestroy () {
      // 手動銷燬 $on 事件,防止屢次觸發
      Bus.$off('someBusMessage', this.someBusMessage)
    }
  }
</script>

4、遞歸組件

組件能夠在本身的 template 模板中調用本身,須要設置 name 選擇。

// 遞歸組件 ComponentRecursion.vue
<template>
  <div>
    <p>遞歸組件</p>
    <ComponentRecursion :count="count + 1" v-if="count < 3"></ComponentRecursion>
  </div>
</template>

<script>
  export default {
    name: "ComponentRecursion",
    props: {
      count: {
        type: Number,
        default: 1
      }
    }
  }
</script>

若是遞歸組件沒有 count 等限制數量,就會拋出錯誤(Uncaught RangeError: Maximum call stack size exceeded)。

父頁面使用該遞歸組件,在 Chrome 中的 Vue Devtools 能夠看到組件遞歸了三次:
clipboard.png

遞歸組件能夠開發未知層級關係的獨立組件,如級聯選擇器和樹形控件等。

5、動態組件

若是將一個 Vue 組件命名爲 Component 會報錯(Do not use built-in or reserved HTML elements as component id: Component),由於 Vue 提供了特殊的元素 <component>來動態掛載不一樣的組件,並使用 is 特性來選擇要掛載的組件。

如下是使用<component>動態掛載不一樣組件的示例:

// parentComponent.vue
<template>
 <div>
    <h1>父組件</h1>
    <component :is="currentView"></component>
    <button @click = "changeToViewB">切換到B視圖</button>
 </div>
</template>

<script>
  import ComponentA from '@/components/ComponentA'
  import ComponentB from '@/components/ComponentB'
  export default {
   components: {
      ComponentA,
      ComponentB
    },
   data() {
      return {
        currentView: ComponentA // 默認顯示組件 A
      }
    },
    methods: {
      changeToViewB () {
        this.currentView = ComponentB // 切換到組件 B
      }
    }
  }
</script>

改變 this.currentView的值就能夠自由切換 AB 組件:
clipboard.png

與之相似的是 vue-router的實現原理,前端路由到不一樣的頁面實際上就是加載不一樣的組件。

要繼續加油呢,少年!

相關文章
相關標籤/搜索