1. 程式人生 > >【Vue】詳解元件的基礎與高階用法

【Vue】詳解元件的基礎與高階用法

構建元件

元件基礎

一個元件由 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 單檔案元件

如果專案中使用打包編譯工具 webpack,那引入 vue-loader 就可以使用 .vue字尾檔案構建元件。一個.vue單檔案元件 (SFC) 示例:

// 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 語法的解析。

註冊元件

手動註冊

元件定義完後,還需要註冊才可以使用,註冊分為全域性和區域性註冊:

// 全域性註冊,任何 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) 建立之前發生。

自動註冊

對於通用模組使用列舉的註冊方式程式碼會非常不方便,推薦使用自動化的全域性註冊。如果專案使用 webpack,就可以使用其中的require.context一次性引入元件資料夾下所有的元件:

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
  )
})

元件通訊

父單向子的 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

子向父的 $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

子孫的鏈與索引

元件的關係有很多時跨級的,這些元件的呼叫形成多個父鏈與子鏈。父元件可以通過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

遞迴元件

元件可以在自己的 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

遞迴元件可以開發未知層級關係的獨立元件,如級聯選擇器和樹形控制元件等。

動態元件

如果將一個 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的實現原理,前端路由到不同的韻實際上就是載入不同的元件。

要繼續加油呢,少年!