0%

Vue系列文章五:Vue Instance與Component

前情提要

前面兩篇講了vue相關的語法,是不是覺得少了點什麼,這篇是重頭戲,介紹Vue Instance宣告時有哪些options可設定,有什麼樣的相關技巧以及Component的應用,筆者就以自身開發角度分享實戰經驗。

使用工具及環境參考[Vue]Vue基本語法

內容

Vue Instance

建立實體

使用new關鍵字建立vue實體

1
2
3
var vm = new Vue({
// options
})

相關屬性介紹

el

最重要且一定要設定的option,vue的作用域,與jquery selector類似方式宣告

1
2
3
4
// 表示vue作用域為id=app之div區塊
new Vue({
el: '#app',
})

data

vue中使用到的參數,皆宣告於此

1
2
3
4
5
6
7
8
new Vue({
el: '#app',
data(){
return {
msg: 'Hello Vue!!'
}
}
})

methods

vue宣告之使用到的function,皆宣告於此

1
2
3
4
5
6
7
8
9
10
11
12
13
new Vue({
el: '#app',
data(){
return {
msg: 'Hello Vue!!'
}
},
methods: {
someMethod: function(){
console.log('Hello Vue!!');
}
}
})

Lifecycle Hook

vue在實體化過程中開放hook讓我們監聽,看官網圖比較順

以上以筆者經驗來說,最常使用的是createdmountedbeforeDestroy這三個事件,使用時機為

  1. created:會Initial頁面上的資料來源,如下拉式選單source
  2. mounted:監聽事件宣告,或者確保vue實體化完才可以做的事情;若分不清楚,在此宣告是最安全的
  3. beforeDestroy:若頁面上有監聽事件則,需於此事件中解除監聽off

computed

若頁面上使用變數為經過運算後才得到值,可以使用computedoption中宣告它,在vue中使用時與data參數無差別,差別在於若運算邏輯中有參考到別的參數則,該參數有變化則會重新運算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
new Vue({
el: '#app',
data(){
return {
msg: 'Hello Vue!!'
}
},
methods: {
someMethod: function(){
console.log('Hello Vue!!');
}
},
computed: {
msgLength: function(){
return this.msg.length;
}
}
})

官網中提到假設computed變數中無參考到其他參數,而是一般function執行,例

1
2
3
4
5
computed: {
now: function () {
return Date.now()
}
}

則比較適合使用methods方式來宣告,才會頁面rerender時會再重新呼叫一次function

watch

監聽參數變化,vue預設會提供新舊值讓我們操作

1
2
3
4
5
6
7
8
watch: {
// 監聽question這個參數
question: function (newQuestion, oldQuestion) {
//邏輯處理
//newQuestion:新設定值
//oldQuestion:最後一次設定值
}
},

methods/computed/watch這三個option一定要慎選使用,多思考一秒會讓你事半功倍,不留下臭蟲

mixin

mixin看官網解釋重點就是mixed這個字,表是我們可以定義一個component,有完整的vue實體化options可以設定,並使用mixinsoption使用我們剛定義好的component。以筆者親身經驗來說,若有共用function的需求,我會定一個mixin用component,裡頭會使用methods宣告共用function。最後,任一個component中使用mixinsoption宣告並使用共用function。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 定義完整的Component當作MyMixin
<template>
<div id="mymixin"></div>
</template>
<script>
export default {
name: "MyMixin",
methods: {
// 這邊定義了一個共用function
hello() {
console.log("Hello vue from mixin!");
}
}
};
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 示範使用上述宣告之MyMixin
<template>
<div id="mixindemo">

</div>
</template>
<script>
import MyMixin from "@/components/MyMixin.vue";
export default {
name: "MixinDemo",
mixins: [MyMixin],
created: function() {
this.hello();
}
};
</script>

執行後的結果

Component介紹

基本上,若使用webpack fulltemplate產生之專案則,每一個.vue檔案都是一個獨立的component,vue instance裡可設定的所有options(除el)皆可設定。

使用技巧

筆者習慣上,依功能區分component,以利達到Component最小化,例如Layout中會抽Navigation、Sidebar、Footer等;若查詢頁面中則會抽Conditions、Pagination等Component。

傳遞資料

以上述情形,會遇到Component間傳遞資料的需求,假設以查詢頁面為例,符合統一頁面設計規則,每個查詢頁面中會放置「新增」按鈕,這種統一需求會一併抽至conditionscomponent,等於每個功能的「新增」url設定需傳至conditionscomponent中。傳遞資料會分為兩種情況,若將import方式引入component則,引入進來的component稱為child component,本身component則稱為parent component,因此會分為父傳子子傳父的傳遞方式。筆者針對這個傳遞資料的示範會使用兩個component完成:conditionscomponentdemo

父傳子

父傳子的方式簡單多了,在child component中使用propsoption設定參數的宣告,parent component中透過v-on的方式指定該參數的值即可。

  • Child ComponentConditions.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<template>
<div id="conditions">
<div class="card">
<div class="card-header">
<!-- 外部連結 -->
<a :href="insertUrl"
class="btn btn-secondary"
target="_blank">
<i class="fa fa-plus"></i> 新增
</a>
<!-- 內部vue router path -->
<!-- <router-link :to="{ path: insertUrl }">
<i class="fa fa-plus"></i> 新增
</router-link> -->
</div>
<div class="card-body">
<form class="form form-horizontal">
<div class="row">
<div class="col-4 form-group">
<label>查詢條件一</label>
<input type="text"
class="form-control">
</div>
<div class="form-group col-4">
<label>查詢條件二</label>
<select class="form-control">
<option value=""></option>
</select>
</div>
</div>
<div class="row">
<div class="col">
<button class="btn btn-danger">
<i class="fa fa-times"></i> 取消
</button>
<button class="btn btn-primary">
<i class="fa fa-search"></i> 查詢
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Conditions",
// 使用props宣告可以使父component傳的參數
props: ["insertUrl"]
};
</script>

  • Parent ComponentComponentDemo.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div id="componentdemo">
<conditions :insertUrl="insertUrl"></conditions>
</div>
</template>
<script>
import Conditions from '@/components/Conditions.vue'
export default {
name: 'ComponentDemo',
components: {
Conditions
},
data(){
return{
insertUrl: 'http://google.com.tw'
}
}
}
</script>

子傳父

以上述例子,跟著筆者繼續完成子傳父的功能吧,假設查詢按鈕按下去後將查詢條件區塊中使用者所填選的值傳送至父component中做查詢動作,我們需要使用$emit的方式通知。

  • Child ComponentConditions.vue
    • 宣告data參數值:conditions
    • 將conditions參數透過v-modelbind至查詢條件區塊中
    • 查詢鈕中加入click事件,使用v-on:click,function名稱為searchProcess
    • searchProcessfunction中emit事件,事件名稱為searchEmit
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      <template>
      <div id="conditions">
      <div class="card">
      <div class="card-header">
      <!-- 外部連結 -->
      <a :href="insertUrl"
      class="btn btn-secondary"
      target="_blank">
      <i class="fa fa-plus"></i> 新增
      </a>
      <!-- 內部vue router path -->
      <!-- <router-link :to="{ path: insertUrl }">
      <i class="fa fa-plus"></i> 新增
      </router-link> -->
      </div>
      <div class="card-body">
      <form class="form form-horizontal">
      <div class="row">
      <div class="col-4 form-group">
      <label>查詢條件一</label>
      <input type="text"
      class="form-control"
      v-model="conditions.query1">
      </div>
      <div class="form-group col-4">
      <label>查詢條件二</label>
      <select class="form-control"
      v-model="conditions.query2">
      <option value=""></option>
      <option value="1">下拉選單一</option>
      <option value="2">下拉選單二</option>
      </select>
      </div>
      </div>
      <div class="row">
      <div class="col">
      <button class="btn btn-danger">
      <i class="fa fa-times"></i> 取消
      </button>
      <button class="btn btn-primary"
      @click.prevent="searchProcess()">
      <i class="fa fa-search"></i> 查詢
      </button>
      </div>
      </div>
      </form>
      </div>
      </div>
      </div>
      </template>
      <script>
      export default {
      name: "Conditions",
      // 使用props宣告可以使父component傳的參數
      props: ["insertUrl"],
      data() {
      return {
      conditions: {}
      };
      },
      methods: {
      searchProcess: function() {
      this.$emit("searchEmit", this.conditions);
      }
      }
      };
      </script>

  • Parent ComponentComponentDemo.vue
    • 接收子component的emit事件:searchEmit,使用v-on的方式
    • 並觸發事件時指定執行該component中的methodssearchProcess
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      <template>
      <div id="componentdemo">
      <conditions :insertUrl="insertUrl"
      @searchEmit="searchProcess"></conditions>
      </div>
      </template>
      <script>
      import Conditions from "@/components/Conditions.vue";
      export default {
      name: "ComponentDemo",
      components: {
      Conditions
      },
      data() {
      return {
      insertUrl: "http://google.com.tw"
      };
      },
      methods: {
      searchProcess: function(conditions) {
      console.log(conditions);
      }
      }
      };
      </script>

      執行後的結果 可以使用chrome偵錯視窗中的vuetab可以看出$emit的歷程,也可以看出$emit中發送的payload

動態載入

vue有提供動態載入component的機制,若有需求是依照某些條件載入不同的component就可以使用動態載入機制。

1
2
// 動態指定currentComponent的值:component名稱
<component v-bind:is="currentComponent"></component>

以筆者經驗是除非有必要性,不然絕不要輕易嘗試,筆者在專案中使用的情境是,因切Layout時,功能區塊必須與頁面標題顯示同個div中,頁面標題使用vue-router中的自定義meta屬性來宣告,因此放在layout中統一處理。

功能區塊可以想成是取消 儲存 列印 匯出報表等該頁面所屬功能,最後設計是每個頁面會搭配一個結尾為Feature的頁面,會去判斷route的值,然後指定該頁面所屬的功能列component,缺點就是Layout檔案中會有一堆component的import及宣告componentsoption,不過官網上的建議Automatic Global Registration of Base Components,實際範例:vue-enterprise-boilerplate或許有解,筆者目前尚未實際使用過,有機會使用後再補上心得,目前是對於動態載入component有點卻步。

結論

藉由這幾篇的觀念解說及經驗分享,筆者相信可以處理大部份的功能,經由這篇的Component介紹,就可以很有系統地去規劃頁面並重用,但傳遞資料需求上面,有父子關係才有辦法做到,但往往有系統的設計會需要無任何關係的component間溝通,需要使用到eventbus的技巧或vuex的store機制,筆者會在套件介紹會寫一篇vuex的實作,再加上會透過vue技巧文章的方式會提出實際碰到問題時的解決方案,希望這系列文章幫助到大家。

範例檔案參考https://github.com/EugeneSu0515/VueDemoApp