Extend
假設今天有兩個元件,他們的差異非常小,
只有少部分不同的話,該怎麼處理呢?
那就先把兩個元件重複的部分抓取出來,接著,用Vue.extend
的技巧將重複的部分存起來。
ex:
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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
| ---HTML--- <div id="app"> <table class="table"> <tbody> <tr is="row-component-one" v-for="(item, key) in data" v-if="key % 2" :item="item" :key="key"></tr> <tr is="row-component-two" v-for="(item, key) in data" v-if="(key - 1) % 2" :item="item" :key="key"></tr> </tbody> </table> </div>
---JavaScript--- <script type="text/x-template" id="row-component"> <tr> <td>{{ item.name }}</td> <td>{{ item.cash | currency | dollarSign }}</td> <td>{{ item.icash | currency | dollarSign }}</td> </tr> </script>
<script type="text/x-template" id="row-component-two"> <tr class="bg-primary text-white"> <td>{{ item.name }}</td> <td>{{ item.cash | currency | dollarSign }}</td> <td>{{ item.icash | currency | dollarSign }}</td> </tr> </script>
<script> var childOne = { props: ['item'], data: function() { return { data: {}, extendData: '這段文字是 extend 得到' } }, template: '#row-component', filters: { dollarSign: function (n) { return `$ ${n}` }, currency: function(n) { return n.toFixed(2).replace(/./g, function(c, i, a) { return i && c !== "." && ((a.length - i) % 3 === 0) ? ',' + c : c; }); } }, mounted: function() { console.log('Extend:', this) } }
var childTwo = { props: ['item'], data: function() { return { data: {}, extendData: '這段文字是 extend 得到' } }, template: '#row-component-two', filters: { dollarSign: function (n) { return `$ ${n}` }, currency: function(n) { return n.toFixed(2).replace(/./g, function(c, i, a) { return i && c !== "." && ((a.length - i) % 3 === 0) ? ',' + c : c; }); } }, mounted: function() { console.log('Extend:', this) } } var app = new Vue({ el: '#app', data: { data: [ { name: '小明', cash: 100, icash: 500, }, { name: '杰倫', cash: 10000, icash: 5000, }, { name: '漂亮阿姨', cash: 500, icash: 500, }, { name: '老媽', cash: 10000, icash: 100, }, ] }, components: { "row-component-one": childOne, "row-component-two": childTwo, }, mounted: function() { console.log('Vue init:', this) } }); </script>
|
元件破壞html結構
首先我想要在這邊先提醒你一下,你可以看到在以上範例,我們在table
的tbody
中的tr
,
是用is
的方式去綁定我們的元件名稱,而不是直接用我們的元件名稱當標籤,塞進去即<row-component-one></row-component-one>
,
這個原因是因為在tbody
裡面的第一層一定要塞tr
,不能是其它的內容,不然table
的html佈局會沒有辦法出現,所以,才需要在tr
標籤中用is
來綁定相對應的 Vue 元件,這邊你要特別注意一下。
OK~~回到上面的例題內容,
你可以看到以上兩個元件,
row-component-one 和 row-component-two,他們倆個定義的內容基本上都一樣,
只有差異在template
引入的內容不一樣。
所以,我們可以將這兩個元件裡面相同的內容,抓出來並貼到Vue.extend
裡面。
變成以下這樣子,我們只更動JS的部分
ex:
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
| <script> var newExtend = Vue.extend({ data: function() { return { data: {}, extendData: '這段文字是 extend 得到' } }, template: '#row-component', filters: { dollarSign: function (n) { return `$ ${n}` }, currency: function(n) { return n.toFixed(2).replace(/./g, function(c, i, a) { return i && c !== "." && ((a.length - i) % 3 === 0) ? ',' + c : c; }); } }, mounted: function() { console.log('Extend:', this) } })
var childOne = { props: ['item'], extends: newExtend }
var childTwo = { props: ['item'], template: '#row-component-two', extends: newExtend } </script>
|
首先,
我們先將childOne的定義內容中,除了props:['item']
以外的內容,都剪下來貼到
Vue.extend
裡面。
第二,我們在ChildOne的定義內容裡面,新增一個屬性extends
,並為這個屬性值
的內容設入你剛剛儲存剪下來的內容的變數newExtend。
如此,就完成了將重複的部分,加到extends
裡面,並利用extends
來引用這些內容。
接著,我們對childTwo的定義內容,
只留下props
和template
的內容,其他的都刪掉,
並新增一個extends
屬性,並為它設入newExtend的內容。
如此,就完成利用extends
來餵兩個不一樣的元件,引入相同的定義內容囉。
另外,我們為childTwo的元件生命週期修改一下
ex:
1 2 3 4 5 6 7 8
| var childTwo = { props: ['item'], template: '#row-component-two', extends: newExtend, mounted: function(){ console.log('childTwo'); } }
|
這時候,你會看到log的結果為
Extend Extend Extend childTwo Extend childTwo Vue init
你會看到你新增的函式也會被執行到。
由此可知,extends
不只會執行生命週期內的函式,如果,在生命週期內再另外加入
其他的函式的話,這些新增加的函式也同樣會被執行。
完成的結果如下
extends - 資料重新定義
我們改寫一下上面案例中的childTwo,我們為它新增資料,看看會怎樣
ex:
1 2 3 4 5 6 7 8 9 10 11 12 13
| var childTwo = { props: ['item'], data: function(){ return { childTwo:'元件2' } }, template: '#row-component-two', extends: newExtend, mounted: function(){ console.log('childTwo'); } }
|
此時,你打開vue的開發者工具,你會發現childTwo的元件除了會有原本元件的資料以外,
也會含有你剛剛新增的childTwo的資料喔。
所以,由此可知,新的元件所新增的資料不僅會擁有它新增的資料內容,更不會把從extends
裡面所繼承的內容給取代掉。
extends - 複寫原本的資料
這邊在稍微修改一下以上範例中childTwo的資料內容
ex:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| var childTwo = { props: ['item'], data:function(){ return{ childTwo:'元件2', extendData: 'childTwo覆蓋原本內容' } }, template: '#row-component-two', extends: newExtend, mounted: function(){ console.log('childTwo'); } }
|
你可以看到,我們在childTwo將它從extends
得到的屬性extendData,在它自己的data
屬性裡面,再重新為它賦值一次,這個時候,你用Vue的開發者介面去看childTwo元件的extendData的屬性值,是你覆蓋完之後的值喔~~
小總結: 資料重新定義 v.s 複寫原本的資料
在以上這兩個小節中,你可以看到,你為extends中,重複的生命週期事件重新定義的話(即extends裡面有定義過mounted hooks,再繼承這個extends的元件中在定義它自己的mounted hooks),不只會保存原本的內容,也同時會呈現你新增的內容。
若你複寫原本的資料的話,就會呈現覆寫完的結果喔。
filters 自訂畫面資料呈現格式
filters
的概念就是在一個數值後面加上一槓 (|) 接著, 再加上一個filters
函式的名稱。
而filters
不只可以重複使用 且 可在同一個數值中可以套多數個filter
函式的功能。
你可以看到以上這個範例,我們希望在table
裡面的數值,加上千分號的效果,
所以,我們先在元件的定義裡新增一個filters
的屬性,
接著,在此屬性裡面新增自訂義的filters
名稱curreny,而在此currency的函式裡面,回傳了將數值做千分號處理的效果,
注意~~ 在這個函式的有一個參數n,它就是將加在 | currency前面的值,當作參數丟進 currency 的函式中。
而這邊丟入的參數就是item.icash。
如此,就可以為該欄加上千分號的效果囉。
另外,有關filters
的效果可以重複使用,你可以看到我們也有為item.cash後面加上了 | currency,故也為此欄加入了千分號的效果了。
另外,我們還想要為這些數值前面加上錢字號$。
在這邊可以看到,我們在元件的filters
裡面,再加入一個dollarSign屬性,並回傳加上錢字號的結果。
那這邊要特別注意的是,dollarSign這個函式傳入的參數,是item.icash和curreny處理完之後的結果,即加完千分號的item.cash結果。
並再為這個結果加上dollarSign的效果。
替元件外的物件也加入filter的效果
以上面的案例為例,如果我們直接將child中定義的currency 和 dollarSign加到HTML中的{{ data[1].cash }}
中,
是不會有效果的,即{{ data[1].cash | currency | dollarSign}}
這樣是不會有效果的。
那要怎麼處理呢,
我們就將這些filters
的方法從原本的局部註冊,改成全域註冊。
你可以看到我們將原本註冊在child裡面的dollarSing和currency方法,拉到外面,
改由Vue.filter
來註冊這兩個方法。
你可以看到在{{ data[1].cash | currency | dollarSign }}
加入這兩個filters
的方法就不會報錯囉。
用以上這種方式,就可以讓所有元件都調用這兩種方法囉。
filter 全域註冊 v.s 局部註冊 寫法不同
以上面的範例,你可以看到如果是全域註冊的話Vue.filter的filter是不用加s的。
而局部註冊的話,在元件中的filters屬性,是要加s的喔!!!
無法寫入的資料,用 set 搞定它
在Vue中,是利用setter
和getter
來監聽元件,並且在該元件的資料被更改時,可以及時更改。
所以,當該資料沒有set
和get
的屬性的話,就代表該資料沒有進入Vue的綁定內。
記得為屬性設預設值
注意,如果你沒有事先為屬性設預設值的話,該屬性的資料就不會有set
和get
的屬性喔。
而當該元件沒有進入Vue的綁定內的話,就會發生雖然該資料有被成功寫入,但因為該筆資料沒有get
和set
的屬性,
也就是沒又進入vue綁定,導致,該元件的內容不會出現在畫面中喔。
ex:
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 69 70 71 72 73 74 75 76 77 78
| ---HTML--- <div id="app"> <table class="table"> <tbody> <tr is="row-component" v-for="(item, key) in data" :item="item" :key="key"></tr> </tbody> </table> </div>
---JavaScript--- <script type="text/x-template" id="row-component"> <tr> <td>{{ item.name }}</td> <td>{{ item.cash }}</td> <td>{{ item.icash }}</td> <td> <span v-if="data.obj">{{ data.obj.name }}</span> <button class="btn btn-sm btn-primary" @click="addData()">寫入資料</button> </td> </tr> </script>
<script> var child = { props: ['item'], template: '#row-component', data: function() { return { data: {} } }, methods: { addData: function() { this.data.obj = { name: this.item.name } console.log(this.data, this); } }, mounted: function() { console.log('Component:', this) } }
var app = new Vue({ el: '#app', data: { data: [ { name: '小明', cash: 100, icash: 500, }, { name: '杰倫', cash: 10000, icash: 5000, }, { name: '漂亮阿姨', cash: 500, icash: 500, }, { name: '老媽', cash: 10000, icash: 100, }, ] }, components: { "row-component": child }, mounted: function() { console.log('Vue init:', this) } }); </script>
|
以上範例你可以看到,當你點擊child元件中的寫入資料的按鈕的時候,會觸發它的addData方法,
這個時候,在addData方法中我們會動態地為data物件新增一個obj屬性,並為這個obj屬性新增一個name屬性,
並將這個name屬性的值,設為當下被點擊元件的name的名稱。
從這邊我們可以看出來,因為,data的obj屬性 和 obj屬性的name屬性,都是動態地被創出來的,所以,
根本不可能為它們預先設定預設值,也就造成obj的name屬性沒有set
和get
的這兩個屬性,進而造成它沒有進入Vue的綁定,
最終,在畫面上是沒有辦法呈現該obj的name的結果。
以上的狀況,常發生在我們利用Ajax抓資料,並再資料被抓進來的時候,順道為它們新增屬性。
所以,我們希望事後補資料或動態地新增屬性到元件的data中的話,我們就需要用set
的語法。
接下來就來介紹Vue.set
要怎麼使用:
this.$set( target, index, value )
這個語法有三個引數
target
: 將資料寫入到target
key
: 該數值在target中的名稱是什麼
value
: 要寫入的內容
藉由以上的方法,你就可以看到this.data的key
值obj,就有了get
和set
的方法,也就代表該資料已經進入Vue的綁定了。
接著,我們為鍵值obj新增了屬性name,並將this.item.name的值設給它。
經過以上set
的設定,我們就可以在按下寫入資料按鈕後,動態的新增這個obj鍵值和它的name屬性,並將它們呈現在畫面中了。
Mixin 混合其它的元件內容
Mixin
跟extends
的功能有點雷同。
但是,extends
是專門給單一元件使用。
而Mixin
是可以混合多個元件一起使用。
ex:
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 69 70 71 72 73 74 75 76 77
| ---HTML--- <div id="app"> <table class="table"> <tbody> <tr is="row-component" v-for="(item, key) in data" :item="item" :key="key"></tr> </tbody> </table> </div>
---JavaScript--- <script type="text/x-template" id="row-component"> <tr> <td>{{ item.name }}</td> <td>{{ item.cash | currency | dollarSign }}</td> <td>{{ item.icash | currency | dollarSign }}</td> </tr> </script>
<script> // mixin 是多個混合的概念
Vue.component('row-component', { props: ['item'], data: function() { return { data: {}, } }, template: '#row-component', filters: { dollarSign: function (n) { return `$ ${n}` }, currency: function(n) { return n.toFixed(2).replace(/./g, function(c, i, a) { return i && c !== "." && ((a.length - i) % 3 === 0) ? ',' + c : c; }); } }, mounted () { console.log('這段是 Mixin 產生') } });
var app = new Vue({ el: '#app', data: { data: [ { name: '小明', cash: 100, icash: 500, }, { name: '杰倫', cash: 10000, icash: 5000, }, { name: '漂亮阿姨', cash: 500, icash: 500, }, { name: '老媽', cash: 10000, icash: 100, }, ] }, mounted: function() { console.log('Vue init:', this) } }); </script>
|
我們使用mixin
改寫要以上的範例
ex:
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 69 70 71 72
| ---HTML--- <div id="app"> <table class="table"> <tbody> <tr is="row-component" v-for="(item, key) in data" :item="item" :key="key"></tr> </tbody> </table> </div>
---JavaScript--- var mixinFilter = { template: '#row-component', filters:{ dollarSign: function (n) { return `$ ${n}` }, currency: function(n) { return n.toFixed(2).replace(/./g, function(c, i, a) { return i && c !== "." && ((a.length - i) % 3 === 0) ? ',' + c : c; }); } } } var mixinMounted = { mounted () { console.log('這段是 Mixin 產生') } } Vue.component('row-component', { props: ['item'], data: function() { return { data: {}, } }, mixins:[mixinFilter,mixinMounted] });
var app = new Vue({ el: '#app', data: { data: [ { name: '小明', cash: 100, icash: 500, }, { name: '杰倫', cash: 10000, icash: 5000, }, { name: '漂亮阿姨', cash: 500, icash: 500, }, { name: '老媽', cash: 10000, icash: 100, }, ] }, mounted: function() { console.log('Vue init:', this) } }); </script>
|
可以看到,我們先宣告兩個物件mixinFilter和mixinMounted,
並把重複的內容塞到他們裡面,
接著,在row-component的元件定義中,寫入資料mixins
,然後,丟一個陣列進去,
那這個陣列裡的元素就是我們剛剛定義的mixinFilter和mixinMounted物件囉,
所以,用這種方式,我們就可以透過mixin
來增加很多內容。
另外,我們新增一個新元件,並將mixin
的內容加進去,再為這個新元件新增資料data
此時,你用Vue開發者工具就可以看到row-component-two元件,有多一個它自己的data資料屬性囉。