摘要: 搞懂Vue組件!javascript
Fundebug經受權轉載,版權歸原做者全部。html
本文主要介紹屬性、事件和插槽這三個vue基礎概念、使用方法及其容易被忽略的一些重要細節。若是你閱讀別人寫的組件,也能夠從這三個部分展開,它們能夠幫助你快速瞭解一個組件的全部功能。前端
本文的代碼請猛戳github博客,紙上得來終覺淺,你們動手多敲敲代碼!vue
prop 定義了這個組件有哪些可配置的屬性,組件的核心功能也都是它來肯定的。寫通用組件時,props 最好用對象的寫法,這樣能夠針對每一個屬性設置類型、默認值或自定義校驗屬性的值,這點在組件開發中很重要,然而不少人卻忽視,直接使用 props 的數組用法,這樣的組件每每是不嚴謹的。java
// 父組件 <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 能夠顯示定義一個或一個以上的數據,對於接收的數據,能夠是各類數據類型,一樣也能夠傳遞一個函數。經過通常屬性實現父向子通訊;經過函數屬性實現子向父通訊git
這是2.4.0 新增的一個API,默認狀況下父做用域的不被認做 props 的特性綁定將會「回退」且做爲普通的 HTML 特性應用在子組件的根元素上。可經過設置 inheritAttrs 爲 false,這些默認行爲將會被去掉。注意:這個選項不影響 class 和 style 綁定。 上個例中,title屬性沒有在子組件中props中聲明,就會默認掛在子組件的根元素上,以下圖所示:github
二者選項裏均可以存放各類類型的數據,當行爲操做改變時,全部行爲操做所用到和模板所渲染的數據同時都會發生同步變化。編程
data 被稱之爲動態數據,在各自實例中,在任何狀況下,咱們均可以隨意改變它的數據類型和數據結構,不會被任何環境所影響。小程序
props 被稱之爲靜態數據,在各自實例中,一旦在初始化被定義好類型時,基於 Vue 是單向數據流,在數據傳遞時始終不能改變它的數據類型,並且不容許在子組件中直接操做 傳遞過來的props數據,而是須要經過別的手段,改變傳遞源中的數據。至於如何改變,咱們接下去詳細介紹:segmentfault
這個概念出如今組件通訊。props的數據都是經過父組件或者更高層級的組件數據或者字面量的方式進行傳遞的,不容許直接操做改變各自實例中的props數據,而是須要經過別的手段,改變傳遞源中的數據。那若是有時候咱們想修改傳遞過來的prop,有哪些辦法呢?
在子組件的 data 中拷貝一份 prop,data 是能夠修改的
export default { props: { type: String }, data () { return { currentType: this.type } } }
在 data 選項裏經過 currentType接收 props中type數據,至關於對 currentType= type進行一個賦值操做,不只拿到了 currentType的數據,並且也能夠改變 currentType數據。
export default { props: { type: String }, computed: { normalizedType: function () { return this.type.toUpperCase(); } } }
以上兩種方法雖能夠在子組件間接修改props的值,但若是子組件想修改數據而且同步更新到父組件,卻無濟於事。在一些狀況下,咱們可能會須要對一個 prop 進行『雙向綁定』,此時就推薦如下這兩種方法:
// 父組件 <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 }"
是沒法正常工做的)。
這是由於在 JavaScript 中對象和數組是經過引用傳入的,因此對於一個數組或對象類型的 prop 來講,在子組件中改變這個對象或數組自己將會影響到父組件的狀態。好比上例中在子組件中修改父組件傳遞過來的數組arr,從而改變父組件的狀態。
對於字面量語法和動態語法,初學者可能在父組件模板中向子組件中傳遞數據時到底加和不加 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
用原生JavaScript事件驅動一般是這樣的流程:
這種模式對業務來講是沒有什麼問題,可是從開發成本和效率來講會比較不理想,特別是在業務系統愈來愈龐大的時候。另外一方面,找節點和修改節點這件事,效率自己就很低,所以出現了數據驅動模式。
Vue的一個核心思想是數據驅動。所謂數據驅動,是指視圖是由數據驅動生成的,咱們對視圖的修改,不會直接操做 DOM,而是經過修改數據,其流程以下:
用戶執行某個操做 -> 反饋到 VM 處理(能夠致使 Model 變更) -> VM 層改變,經過綁定關係直接更新頁面對應位置的數據
能夠簡單地理解:數據驅動不是操做節點的,而是經過虛擬的抽象數據層來直接更新頁面。主要就是由於這一點,數據驅動框架才得以有較快的運行速度(由於不須要去折騰節點),而且能夠應用到大型項目。
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>
插槽分爲普通插槽和做用域插槽,其實二者很相似,只不過做用域插槽能夠接受子組件傳遞過來的參數。
咱們不妨經過一個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.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專一於JavaScript、微信小程序、微信小遊戲、支付寶小程序、React Native、Node.js和Java線上應用實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了10億+錯誤事件,付費客戶有陽光保險、荔枝FM、掌門1對一、核桃編程、微脈、青團社等衆多品牌企業。歡迎免費試用!