項目中沒有從零開始封裝一個組件,本文記錄一下 Vue 組件封裝的基本實踐和一些組件的相關知識。主要涉及如下知識點:javascript
使用第三方計數庫 countup.js 建立一個 count-to
組件對以上知識進行總結。css
在組件文件夾 component
下建立一個與組件名相同的文件,文件夾內必須有 index.js
,並將組件導入到該文件中,這樣方便咱們引用組件。html
count-to
文件夾內:vue
//index.js
import CountTo from './count-to.vue'
export default CountTo
複製代碼
使用組件時,只需這樣引入:java
import CountTo from "_c/count-to";// _c 是組件存放路徑
複製代碼
props 定義了組件可配置
的數據,肯定的組件的核心功能。封裝組件時,props 推薦寫成對象形式,方便對數據進行驗證,提升了代碼健壯性也能明確如何使用。react
常見的檢查類型:Number
、String
、Boolean
、Array
、Object
、Date
、Function
、Symbol
、構造函數
。null|undefined
會經過全部類型。git
還能夠自定義驗證函數,指定是否必須和默認值。github
props:{
// 多個可能的類型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 帶有默認值的數字
propD: {
type: Number,
default: 100
},
// 帶有默認值的對象
propE: {
type: Object,
// 對象或數組默認值必須從一個工廠函數獲取
default: function () {
return { message: 'hello' }
}
},
// 自定義驗證函數
propF: {
validator: function (value) {
// 這個值必須匹配下列字符串中的一個
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
複製代碼
經過閱讀 countUP文檔,瞭解到構造函數CountUp
的參數web
CountUp(eleDOM,startValue,endValue,decimals,duration,options);// eleDOM 是數值顯示的元素;endValue 是數值的最終值,這兩個參數必須的。
複製代碼
組件代碼以下:segmentfault
<template>
<div>
<span :id="eleId"></span>
</div>
</template>
<script> import CountUp from "countup"; export default { name: "CountTo", props: { /** * @description 起始值 */ startValue: { type: Number, default: 0 }, /** * @description 終止值 */ endValue: { type: Number, required: true }, /** * @description 小數點後保留幾位小數(精度) */ decimals: { type: Number, default: 0 }, /** * @description 漸變時長(秒) */ duration: { type: Number, default: 1 }, /** *@description 變速效果 */ useEasing: { type: Boolean, default: false }, /** *@description 分組 */ useGrouping: { type: Boolean, default: true }, /** *@description 分組符號 2,2234 */ separator: { type: String, default: "," }, /** *@description 整數小數分隔符 34.56 */ decimal: { type: String, default: "." }, /** * @description 動畫延遲(秒) */ delay: { type: Number, default: 0 }, }, data() { return {}; }, computed: { eleId() { //使用 this.uid 生成全局惟一id return `count_up_uid${this._uid}`; }, }, mounted() { //TODO: this.$nextTick this.$nextTick(() => { let options = { useEasing: this.useEasing, useGrouping: this.useGrouping, separator: this.separator, decimal: this.decimal }; this.counter = new CountUp( this.eleId, this.startValue, this.endValue, this.decimals, this.duration, options ); }); } }; </script>
複製代碼
代碼說明: this._uid
用於生成組件內惟一
的id值,可用做元素的id,值是遞增的。 this.$nextTick
函數接收一個回調函數做爲參數,回調函數會在 DOM更新
以後執行,若是某些操做必須在DOM更新以後,可將這些操做做爲其參數。
計數組件的基本功能就知足了。
這樣使用組件:
<template>
<div>
<count-to :end-value="endValue" :decimals="decimals" :duration="5" title="這個會掛載到組件根元素上">
</count-to>
</div>
</template>
<script> import CountTo from '_c/count-to' export default { name: 'count_to', components: { CountTo }, data() { return { endValue: 4000, decimals: 2, className: '', } }, } </script>
複製代碼
<count-to :end-value="endValue" :decimals="decimals" :duration="5"></count-to>
複製代碼
prop 的命名:
組件中使用小駝峯
命名,傳遞值是使用-
。
關於 props 傳遞靜態值:
不使用 v-bind
指令:
傳遞的是靜態值,是一個字符串字常量,而不是變量,而使用:
指令傳遞的值,是有類型的。:duration="5"
傳遞是 數值 5,duration="5"
傳遞字符串5
。 duration="true"
傳遞的是字符串true
而不是 Boolean 值真值。
默認值:
傳遞是引用類型的值(對象和數組),默認值須要使用一個工廠函數返回一個引用類型的值。
inheritAttrs:
若是傳遞一個組件中沒有聲明的屬性,該屬性會掛載都組件元素上,可在組件中將inheritAttrs
設置爲 false
取消這一行爲。上面的 title
屬性會掛載到組件的 div
上。該屬性不該 style 和 calss 的傳遞。
<count-to title="會掛載到組件的根元素上" test="test" :end-value="endValue" :decimals="decimals" :duration="5"> </count-to>
複製代碼
title 會成爲count-to
組件的根元素的屬性:
<div title="這是標題" test="測試">
<span id="count_up_uid14" >10,000.00</span>
</div>
複製代碼
$attrs 接收沒有聲明的屬性
title 和 test 屬性沒有在組件中聲明,依然能夠在組件中使用 attrs
接收到些屬性: <span>沒有props接收的父組件數據:{{$attrs}}</span><br/>
最後的結果:
<div title="這是標題" test="測試">
<span>沒有props接收的父組件數據:{
"title": "這是標題",
"test": "測試"
}</span><br>
<span id="count_up_uid14">10,000.00</span>
</div>
複製代碼
inheritAttrs: false 和 $attrs 結合使用:
有了 inheritAttrs: false 和 $attrs,你就能夠手動決定這些特性會被賦予哪一個元素,而不須要聲明變量接收。
{% raw %}
See the Pen $attrs使用 by JackZhouMine (@JackZhouMine) on CodePen.
{% endraw %}props 從父級組件入,傳入的值由父級組件維護,不容許在子組件中直接操做,是否必需和數據類型都是肯定的,咱們不能改變。
data 是組件內部維護的狀態,組件可直接操做,可隨時改變值、類型等。
相同點:都是組件的屬性,改變二者都會響應到模板上。
Vue 不容許在子組件中直接操做 props ,不然會報錯,由於父組件和子組件均可直接操做 props,會使得 props 的管理變得混亂。可經過一些間接的方式操做 props:
以上兩種方式,修改後的值,是不能會響應到父組件的,想要在父級組件中也看到修改,須要用到下面的方式:
傳遞props 時加上 .sync
修飾符,在子組件內部使用 $emit
更新 props。
使用 .sync
須要注意:
v-bind:title.sync="doc.title + '!'"
;v-bind.sync="{ title: doc.title }"
。傳遞數組和對象,在子組件中修改他們,會直接反應到父組件上。
傳統的web開發使用事件驅動:
Vue 的核心思想是數據驅動,視圖由數據決定。MVVM 架構的頁面變化流程:
View(用戶操做) → 執行 DOMlistenrs (ViewModel) → Data 改變 (Model)→ View 改變。
組件和綁定原生事件和自定義事件,綁定原生事件時,須要添加native
修飾符。
能夠在組件的原生事件處理器中觸發一個自定義事件,就能在父級組件中監聽該事件,執行相關操做。
在 count-to
聲明一個 changeValue
事件:
增長一個按鈕:
<button @click="add">+</button>
複製代碼
在事件處理器add
中觸發一個自定義事件:
add() {
this.$emit("changeValue", Math.random() * 100);
}
複製代碼
$emit
的第一個參數是事件名稱,第二個參數是傳遞到該事件監聽器的參數。
在組件上監聽 changValue
:
<template>
<div>
<count-to :end-value="endValue" :decimals="decimals" :duration="5" @changeValue="changeValue">
</count-to>
</div>
</template>
<script> import CountTo from '_c/count-to' export default { name: 'count_to', components: { CountTo }, data() { return { endValue: 4000, decimals: 2, } }, methods: { changeValue(value) { this.endValue += value } }, } </script>
複製代碼
自定義一個更新結束事件:
<script> import CountUp from "countup"; export default { name: "CountTo", methods: { getCount() { //使用 id 獲取 DOM let span = document.getElementById(this.eleId); let currentValue = Number.parseFloat(span.innerText.split(",").join("")); return currentValue.toFixed(this.decimals); }, emitEnd() { this.$emit("on-end", this.getCount()); // this.$emit('on-end', this.endValue) 使用 endValue 不是 庫處理後的值,全部使用 DOM 元素獲取更新後的值 }, }, // 監聽 props 屬性的變化 watch: { endValue(newValue) { //update 是庫的方法 this.counter.update(newValue); setTimeout(() => { this.emitEnd(); }, this.duration * 1000 + 2); } } }; </script>
複製代碼
在組件上使用監聽on-end
:
<template>
<div>
<count-to :end-value="endValue" :decimals="decimals" :duration="5" @on-end="endUp">
</count-to>
</div>
</template>
<script> import CountTo from '_c/count-to' export default { name: 'count_to', components: { CountTo }, data() { return { endValue: 4000, decimals: 2, } }, methods: { // 更新接收後,會觸發自定義事件,而後執行該函數 endUp(value) { console.log('endValue => ', value); }, }, } </script>
複製代碼
change
事件同步數據;<!-- 阻止單擊事件繼續傳播 -->
<a v-on :click.stop="doThis"></a>
<!-- 提交事件再也不重載頁面 -->
<form v-on :submit.prevent="onSubmit"></form>
<!-- 修飾符能夠串聯 -->
<a v-on:click.stop.prevent="doThat"></a>
複製代碼
props 傳遞普通的數據類型,插槽提供了傳遞 HTML 代碼
的方式,父組件中給的插槽內容,會被放置到子組件的指定爲位置。
父組件決定是否顯示插槽和怎樣顯示,子組件決定插槽顯示的位置。
三種插槽:
咱們如今想要在 數值左邊顯示一個從父級組件傳遞到組件中的文字提示,數值右邊顯示人民幣符號。
可以使用插槽接收文字提示和人民幣符號:
<template>
<div>
<!-- 匿名插槽 找不到放置的位置,就放在這裏-->
<slot></slot>
<span :id="eleId"></span>
<slot name="right"></slot>
<!-- 命名插槽-->
</div>
</template>
複製代碼
在父級組件傳遞插槽內容:
<template>
<div>
<count-to :end-value="endValue" :decimals="decimals" :duration="5">
<span>金額:</span>
<span slot="right"> ¥</span>
</count-to>
</div>
</template>
複製代碼
最後的html是這樣的:
<div>
<span>金額:</span>
<span id="count_up_uid13" >4,000.00</span>
<span> ¥</span>
</div>
複製代碼
不傳遞插槽內容時,能夠在組件中設置一個默認的插槽內容:
<template>
<div>
<slot>獎金額度:</slot>
<span :id="eleId"></span>
<slot name="right"> ¥</slot>
</div>
</template>
複製代碼
父級組件的做用域和子組件的做用是獨立的,在父級組件的插槽內容中,獲取不到子組件的數據。
<template>
<div>
<count-to :end-value="endValue" :decimals="parentDecimals" :duration="5">
<span>精確到幾位小數:{{parentDecimals}}</span>
<span slot="right">{{decimals}}</span>
</count-to>
</div>
</template>
複製代碼
parentDecimals
是父級組件中的屬性,插槽內容屬於父級做用域,可獲取父級的數據; decimals
是子級組件中的屬性,插槽內容屬於父級做用域,獲取不到值;
想要在父級插槽內容中獲取子組件的數據,就須要用到做用域插槽。
如今想要把數值前面的文字從父級組件傳遞到子組件,而且還要傳遞文字的顏色:
text: {
name: "本月工資",
color: "#F4D03F"
},
複製代碼
子組件這樣定義:
<template>
<div>
<!--向父級組件傳遞text 並起了名字-->
<slot v-bind="text" name="left">獎金額度:</slot>
<span :id="eleId" ref="number"></span>
<slot name="right"> 元</slot>
</div>
</template>
<script> import CountUp from "countup"; export default { name: "CountTo", props: { //增長 prop text:{ type:Object, default:()=>{} }, } }; </script>
複製代碼
這樣使用組件:
<template>
<div>
<count-to :end-value="endValue" :decimals="decimals" :duration="5" :text="text" >
<template slot-scope="data" slot="left">
<span :style="{color:data.color}">{{data.name}}:</span>
</template>
<span slot="right">¥</span>
</count-to>
</div>
</template>
<script> import CountTo from "_c/count-to"; export default { name: "count_to", components: { CountTo }, data() { return { text: { name: "本月工資", color: "#F4D03F" }, endValue: 4000, decimals: 2, }; } }; </script>
複製代碼
<slot v-bind="text">獎金額度:</slot>
,向父級組件傳遞數據; slot-scope="data"
用來接收插槽傳遞到父組件的數據;
在 2.6.0 中,咱們爲具名插槽和做用域插槽引入了一個新的統一的語法 (即 v-slot 指令)。它取代了 slot 和 slot-scope 。
子組件:
<template>
<div>
<!-- 向父級組件傳遞 textFromChild -->
<slot :textFromChild="text" name="left">獎金額度:</slot>
<span :id="eleId" ref="number"></span>
<slot name="right"> 元</slot>
</div>
</template>
<script> import CountUp from "countup"; export default { name: "CountTo", props: { //增長 prop text:{ type:Object, default:()=>{} }, } }; </script>
複製代碼
這樣使用組件:
<template>
<div>
<count-to :end-value="endValue" :decimals="decimals" :duration="5" :text="text" >
<template v-slot:left="{textFromChild}">
<span :style="{color:textFromChild.color}">{{textFromChild.name}}:</span>
</template>
<span slot="right">¥</span>
</count-to>
</div>
</template>
複製代碼
子組件傳遞過來的變量被放置在一個對象中,使用解構賦值的方式提取出來。
<template v-slot:left="{textFromChild}">
<span :style="{color:textFromChild.color}">{{textFromChild.name}}:</span>
</template>
複製代碼
v-slot
指令後跟一個 slot 的名字,插槽具備名字時,可簡寫爲#
。
<template #left="{textFromChild}">
<span :style="{color:textFromChild.color}">{{textFromChild.name}}:</span>
</template>
複製代碼
無論有幾個插槽,都把插槽內容放置在 template
中是很好的作法。
使用this_uid
其餘字母,可成組件內惟一的id。 count-to
組件中,咱們使用計算屬性,設置 span 的 id。
eleId() {
//使用 this.uid 生成全局惟一id
return `count_up_uid${this._uid}`;
},
複製代碼
在組件內部,能夠經過 id 或者 class 等獲取到 dom,可是不推薦這麼作。可經過ref
屬性,獲取到DOM
,更加簡潔,而且能夠直接經過ref
獲取組件或者DOM
元素。
在下面的函數中獲取DOM:
getCount() {
// TODO: 獲取 DOM
//使用 ref 屬性獲取 DOM 元素
// console.log(this.$refs.number.innerText)
// return this.$refs.number.innerText
//使用 id 獲取 DOM
let span = document.getElementById(this.eleId);
let currentValue = Number.parseFloat(span.innerText.split(",").join(""));
return currentValue.toFixed(this.decimals);
},
複製代碼
this.$nextTick
接收一個回調函數做爲參數,參數會在 Vue 完成DOM 更新後當即調用。若是某些操做是依賴DOM更新後的,能夠把這些操做放在回調函數裏執行。
Vue.$nexttick
全局的,this.$nexttick
是局部的。
var vm = new Vue({
el: '#example',
data: {
message: '123'
}
})
vm.message = 'new message' // 更改數據
vm.$el.textContent === 'new message' // false 此時DOM還沒渲染
Vue.nextTick(function () {
vm.$el.textContent === 'new message' // true
})
複製代碼
Vue DOM 的更新是異步的,數據變化後,組件不會當即渲染,而是在事件隊列刷新時,在下一個事件循環 tick
中渲染。
$nexttick
返回一個 Promise,可以使用 await
關鍵詞調用。
methods: {
updateMessage: async function () {
this.message = '已更新'
console.log(this.$el.textContent) // => '未更新'
await this.$nextTick()
console.log(this.$el.textContent) // => '已更新'
}
}
複製代碼
有時候須要再父級組件中調用子組件的方法。能夠在使用組件時指定 ref
,而後使用 ref
調用。 好比調用組件的暫停方法,使得數據變化暫停。
在組件中定義暫停方法:
<template>
<div>
<slot :textFromChild="text" name="left">獎金額度:</slot>
<span :id="eleId" ref="number" :class="countClass"></span>
<slot name="right"> 元</slot>
</div>
</template>
<script> import CountUp from "countup"; export default { name: "CountTo", data() { return {}; }, methods: { //TODO: 在父級組件中使用封裝組件內部的方法 // 在父級組件中調用該方法,實現暫停 pause() { this.counter.pauseResume(); } } }; </script>
複製代碼
在父組件中使用調用組件暫停方法。
<template>
<div>
<count-to :end-value="endValue" :decimals="decimals" :duration="5" ref="countTo" >
<!-- 指定 ref -->
<template #left="{textFromChild}">
<span :style="{color:textFromChild.color}">{{textFromChild.name}}:</span>
</template>
<span slot="right">¥</span>
</count-to>
<button @click="pasue">暫停</button>
</div>
</template>
<script> import CountTo from "_c/count-to"; export default { name: "count_to", components: { CountTo }, data() { return { endValue: 4000, decimals: 2, }; }, methods: { pasue() { // 使用 refs 訪問組件,而後調用器方法 this.$refs.countTo.pause(); } } }; </script>
複製代碼
組件使用樣式,用三種方式:
外部樣式兩種方法引入: 在 script
標籤中引入和在 style
標籤中引入。
<template>
<div>
<slot :textFromChild="text" name="left">獎金額度:</slot>
<!-- 將 props 中的類綁定到 class 上 -->
<span :id="eleId" ref="number" :class="countClass"></span>
<slot name="right"> 元</slot>
</div>
</template>
<script> //引入樣式方法一: // import './count-to.css' import CountUp from "countup"; export default { name: "CountTo", inheritAttrs: true, //不讓父做用域的屬性掛載到組件的根元素上 props: { /** * @description 自定義樣式類名 */ className: { type: String, default: "" } } }; </script>
<style lang="css"> /* 引入樣式方法二 */ /* @import './count-to.css' */ /* 內部樣式 */ .count-to-number { color: red; font-size: 30px; } </style>
複製代碼
經過 props 傳遞類名,實際是在父級組件中指定使用內部樣式中的哪一個類。
經過 style
也能夠應用樣式到組件上。
封裝一個組件 props 和 data 決定了組件的核心功能,插槽用於向組件傳遞 html 標籤,使得組件更加具備擴展性。經過事件咱們能夠對組件進行某些操做。改天分析一個第三方組件,好好體會一下這些概念。