摘要: 搞懂Vue元件!
- 作者:浪裡行舟
- 原文:詳解vue元件三大核心概念
Fundebug經授權轉載,版權歸原作者所有。
前言
本文主要介紹屬性、事件和插槽這三個vue基礎概念、使用方法及其容易被忽略的一些重要細節。如果你閱讀別人寫的元件,也可以從這三個部分展開,它們可以幫助你快速瞭解一個元件的所有功能。
本文的程式碼請猛戳github部落格,紙上得來終覺淺,大家動手多敲敲程式碼!
一、屬性
1. 自定義屬性props
prop 定義了這個元件有哪些可配置的屬性,元件的核心功能也都是它來確定的。寫通用元件時,props 最好用物件的寫法,這樣可以針對每個屬性設定型別、預設值或自定義校驗屬性的值,這點在元件開發中很重要,然而很多人卻忽視,直接使用 props 的陣列用法,這樣的元件往往是不嚴謹的。
// 父元件
<props name='屬性'
:type='type'
:is-visible="false"
:on-change="handlePropChange"
:list=[22,33,44]
title="屬性Demo"
class="test1"
:class="['test2']"
:style="{ marginTop: '20px' }" //注意:style 的優先順序是要高於 style
style="margin-top: 10px">
</props>
// 子元件
props: {
name: String,
type: {
  //從父級傳入的 type,它的值必須是指定的 'success', 'warning', 'danger'中的一個,如果傳入這三個以外的值,都會丟擲一條警告
validator: (value) => {
return ['success', 'warning', 'danger'].includes(value)
}
},
onChange: {
//對於接收的資料,可以是各種資料型別,同樣也可以傳遞一個函式
type: Function,
default: () => { }
},
isVisible: {
type: Boolean,
default: false
},
list: {
type: Array,
// 物件或陣列預設值必須從一個工廠函式獲取
default: () => []
}
}
複製程式碼
從上面的例中,可以得出props 可以顯示定義一個或一個以上的資料,對於接收的資料,可以是各種資料型別,同樣也可以傳遞一個函式。通過一般屬性實現父向子通訊;通過函式屬性實現子向父通訊
2. inheritAttrs
這是2.4.0 新增的一個API,預設情況下父作用域的不被認作 props 的特性繫結將會“回退”且作為普通的 HTML 特性應用在子元件的根元素上。可通過設定 inheritAttrs 為 false,這些預設行為將會被去掉。注意:這個選項不影響 class 和 style 繫結。 上個例中,title屬性沒有在子元件中props中宣告,就會預設掛在子元件的根元素上,如下圖所示:
3. data與props區別
- 相同點
兩者選項裡都可以存放各種型別的資料,當行為操作改變時,所有行為操作所用到和模板所渲染的資料同時都會發生同步變化。
- 不同點
data 被稱之為動態資料,在各自例項中,在任何情況下,我們都可以隨意改變它的資料型別和資料結構,不會被任何環境所影響。
props 被稱之為靜態資料,在各自例項中,一旦在初始化被定義好型別時,基於 Vue 是單向資料流,在資料傳遞時始終不能改變它的資料型別,而且不允許在子元件中直接操作 傳遞過來的props資料,而是需要通過別的手段,改變傳遞源中的資料。至於如何改變,我們接下去詳細介紹:
4. 單向資料流
這個概念出現在元件通訊。props的資料都是通過父元件或者更高層級的元件資料或者字面量的方式進行傳遞的,不允許直接操作改變各自例項中的props資料,而是需要通過別的手段,改變傳遞源中的資料。那如果有時候我們想修改傳遞過來的prop,有哪些辦法呢?
- 方法1:過渡到 data 選項中
在子元件的 data 中拷貝一份 prop,data 是可以修改的
export default {
props: {
type: String
},
data () {
return {
currentType: this.type
}
}
}
複製程式碼
在 data 選項裡通過 currentType接收 props中type資料,相當於對 currentType= type進行一個賦值操作,不僅拿到了 currentType的資料,而且也可以改變 currentType資料。
- 方法2:利用計算屬性
export default {
props: {
type: String
},
computed: {
normalizedType: function () {
return this.type.toUpperCase();
}
}
}
複製程式碼
以上兩種方法雖可以在子元件間接修改props的值,但如果子元件想修改資料並且同步更新到父元件,卻無濟於事。在一些情況下,我們可能會需要對一個 prop 進行『雙向繫結』,此時就推薦以下這兩種方法:
- 方法3:使用.sync
// 父元件
<template>
<div class="hello">
<div>
<p>父元件msg:{{ msg }}</p>
<p>父元件陣列:{{ arr }}</p>
</div>
<button @click="show = true">開啟model框</button>
<br />
<demo :show.sync="show" :msg.sync="msg" :arr="arr"></demo>
</div>
</template>
<script>
import Demo from "./demo.vue";
export default {
name: "Hello",
components: {
Demo
},
data() {
return {
show: false,
msg: "模擬一個model框",
arr: [1, 2, 3]
};
}
};
</script>
// 子元件
<template>
<div v-if="show" class="border">
<div>子元件msg:{{ msg }}</div>
<div>子元件陣列:{{ arr }}</div>
<button @click="closeModel">關閉model框</button>
<button @click="$emit('update:msg', '浪裡行舟')">
改變文字
</button>
<button @click="arr.push('前端工匠')">改變陣列</button>
</div>
</template>
<script>
export default {
props: {
msg: {
type: String
},
show: {
type: Boolean
},
arr: {
type: Array //在子元件中改變傳遞過來陣列將會影響到父元件的狀態
}
},
methods: {
closeModel() {
this.$emit("update:show", false);
}
}
};
複製程式碼
父元件向子元件 props 裡傳遞了 msg 和 show 兩個值,都用了.sync 修飾符,進行雙向繫結。
不過.sync 雖好,但也有限制,比如:
1)不能和表示式一起使用(如v-bind:title.sync="doc.title + '!'"
是無效的);
2)不能用在字面量物件上(如v-bind.sync="{ title: doc.title }"
是無法正常工作的)。
- 方法4:將父元件中的資料包裝成物件傳遞給子元件
這是因為在 JavaScript 中物件和陣列是通過引用傳入的,所以對於一個陣列或物件型別的 prop 來說,在子元件中改變這個物件或陣列本身將會影響到父元件的狀態。比如上例中在子元件中修改父元件傳遞過來的陣列arr,從而改變父元件的狀態。
5. 向子元件中傳遞資料時加和不加 v-bind?
對於字面量語法和動態語法,初學者可能在父元件模板中向子元件中傳遞資料時到底加和不加 v-bind 會感覺迷惑。
v-bind:msg = 'msg'
這是通過 v-bind 進行傳遞資料並且傳遞的資料並不是一個字面量,雙引號裡的解析的是一個表示式,同樣也可以是例項上定義的資料和方法(其實就是引用一個變數)。
msg='浪裡行舟'
這種在沒有 v-bind 的模式下只能傳遞一個字面量,這個字面量只限於 String 類量,字串型別。那如果想通過字面量進行資料傳遞時,如果想傳遞非String型別,必須props名前要加上v-bind,內部通過例項尋找,如果例項方沒有此屬性和方法,則預設為對應的資料型別。
:msg='11111' //Number
:msg='true' //Bootlean
:msg='()=>{console.log(1)}' //Function
:msg='{a:1}' //Object
複製程式碼
二、事件
1. 事件驅動與資料驅動
用原生JavaScript事件驅動通常是這樣的流程:
- 先通過特定的選擇器查詢到需要操作的節點 -> 給節點新增相應的事件監聽
- 然後使用者執行某事件(點選,輸入,後退等等) -> 呼叫 JavaScript 來修改節點
這種模式對業務來說是沒有什麼問題,但是從開發成本和效率來說會比較不理想,特別是在業務系統越來越龐大的時候。另一方面,找節點和修改節點這件事,效率本身就很低,因此出現了資料驅動模式。
Vue的一個核心思想是資料驅動。所謂資料驅動,是指檢視是由資料驅動生成的,我們對檢視的修改,不會直接操作 DOM,而是通過修改資料,其流程如下:
使用者執行某個操作 -> 反饋到 VM 處理(可以導致 Model 變動) -> VM 層改變,通過繫結關係直接更新頁面對應位置的資料
可以簡單地理解:資料驅動不是操作節點的,而是通過虛擬的抽象資料層來直接更新頁面。主要就是因為這一點,資料驅動框架才得以有較快的執行速度(因為不需要去折騰節點),並且可以應用到大型專案。
2. 修飾符事件
Vue事件分為普通事件和修飾符事件,這裡我們主要介紹修飾符事件。
Vue 提供了大量的修飾符封裝了這些過濾和判斷,讓開發者少寫程式碼,把時間都投入的業務、邏輯上,只需要通過一個修飾符去呼叫。我們先來思考這樣問題:怎樣給這個自定義元件 custom-component 繫結一個原生的 click 事件?
<custom-component>元件內容</custom-component>
複製程式碼
如果你的回答是<custom-component @click="xxx">
,那就錯了。這裡的 @click 是自定義事件 click,並不是原生事件 click。繫結原生的 click 是這樣的:
<custom-component @click.native="xxx">元件內容</custom-component>
複製程式碼
實際開發過程中離不開事件修飾符,常見事件修飾符有以下這些:
- 表單修飾符
1).lazy
在預設情況下,v-model
在每次 input
事件觸發後將輸入框的值與資料進行同步 。你可以新增 lazy
修飾符,從而轉變為使用 change
事件進行同步。適用於輸入完所有內容後,游標離開才更新檢視的場景。
2).trim
如果要自動過濾使用者輸入的首尾空白字元,可以給 v-model 新增 trim 修飾符:
<input v-model.trim="msg">
複製程式碼
這個修飾符可以過濾掉輸入完密碼不小心多敲了一下空格的場景。需要注意的是,它只能過濾首尾的空格!首尾,中間的是不會過濾的。
3).number
如果想自動將使用者的輸入值轉為數值型別,可以給 v-model 新增 number 修飾符:
<input v-model.number="value" type="text" />
複製程式碼
從上面例子,可以得到如果你先輸入數字,那它就會限制你輸入的只能是數字。如果你先輸入字串,那它就相當於沒有加.number
- 事件修飾符
<!-- 阻止單擊事件繼續傳播 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再過載頁面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修飾符可以串聯 -->
<a v-on:click.stop.prevent="doThat"></a>
複製程式碼
三、插槽
插槽分為普通插槽和作用域插槽,其實兩者很類似,只不過作用域插槽可以接受子元件傳遞過來的引數。
1. 作用域插槽
我們不妨通過一個todolist的例子來了解作用域插槽。如果當item選中後,文字變為黃色(如下圖所示),該如何實現呢?
// 父元件
<template>
<div class="toList">
<input v-model="info" type="text" /> <button @click="addItem">新增</button>
<ul>
<TodoItem v-for="(item, index) in listData" :key="index">
<template v-slot:item="itemProps"> // 這是個具名插槽
// 其中itemProps的值就是子元件傳遞過來的物件
<span
:style="{
fontSize: '20px',
color: itemProps.checked ? 'yellow' : 'blue'
}"
>{{ item }}</span
>
</template>
</TodoItem>
</ul>
</div>
</template>
<script>
import TodoItem from "./TodoItem";
export default {
components: {
TodoItem
},
data() {
return {
info: "",
listData: []
};
},
methods: {
addItem() {
this.listData.push(this.info);
this.info = "";
}
}
};
</script>
// 子元件
<template>
<div>
<li class="item">
<input v-model="checked" type="checkbox" />
<slot name="item" :checked="checked"></slot> // 將checked的值傳遞給父元件
</li>
</div>
</template>
<script>
export default {
data() {
return {
checked: false
};
}
};
</script>
複製程式碼
值得注意:v-bind:style 的物件語法十分直觀——看著非常像 CSS,但其實是一個 JavaScript 物件。CSS 屬性名可以用駝峰式 (camelCase) 或短橫線分隔 (kebab-case,記得用引號括起來) 來命名。
2. v-slot新語法
在 2.6.0 中,我們為具名插槽和作用域插槽引入了一個新的統一的語法 (即 v-slot
指令)。它取代了 slot
和 slot-scope
。
我們通過一個例子介紹下預設插槽、具名插槽和作用域插槽的新語法:
// 父元件
<template>
<div class="helloSlot">
<h2>2.6 新語法</h2>
<SlotDemo>
<p>預設插槽:default slot</p>
<template v-slot:title>
<p>具名插槽:title slot1</p>
<p>具名插槽:title slot2</p>
</template>
<template v-slot:item="props">
<p>作用域插槽:item slot-scope {{ props }}</p>
</template>
</SlotDemo>
</div>
</template>
<script>
import Slot from "./slot";
export default {
components: {
SlotDemo: Slot
}
};
</script>
// 子元件
<template>
<div>
<slot />
<slot name="title" />
<slot name="item" :propData="propData" />
</div>
</template>
<script>
export default {
data() {
return {
propData: {
value: "浪裡行舟"
}
};
}
};
</script>
複製程式碼
給大家推薦一個好用的BUG監控工具Fundebug,歡迎免費試用!
參考
關於Fundebug
Fundebug專注於JavaScript、微信小程式、微信小遊戲、支付寶小程式、React Native、Node.js和Java線上應用實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了10億+錯誤事件,付費客戶有陽光保險、荔枝FM、掌門1對1、核桃程式設計、微脈、青團社等眾多品牌企業。歡迎免費試用!