我們都知道函數(shù)的封裝和重用峭咒,將具有相同功能的抽象成函數(shù)方法税弃,在需要的時(shí)候直接傳遞不同的參數(shù)進(jìn)行調(diào)用即可。我們同時(shí)應(yīng)該也清楚一些無益的體力勞動(dòng)我們可以避免凑队,簡單的封裝可以使代碼量更少则果,在有助于性能優(yōu)化的同時(shí)還可以減少錯(cuò)誤的發(fā)生。我們毋庸置疑:代碼寫得越多漩氨,出現(xiàn)bug的幾率越大西壮。
當(dāng)然在我們開發(fā)頁面的時(shí)候,具有類似功能的我們可以抽象成公共組件叫惊,例如一個(gè)小彈窗款青,ip輸入......乃至整個(gè)頁面邏輯的封裝,這樣我們就能大容量的減輕代碼的耦合霍狰。
我理解的所謂公共組件其實(shí)就是這個(gè)組件在被引用的時(shí)候可能涉及到所有功能的并集抡草,也就是所有的功能點(diǎn),這樣我們會(huì)聯(lián)想到我們上學(xué)時(shí)候所學(xué)的交集和并集蔗坯。接下來我們就拿一個(gè)簡單的例子說明下康震,例如一個(gè)修改密碼的彈框,有這樣一個(gè)場景:我們在登錄的時(shí)候我們利用默認(rèn)密碼或者安全不太強(qiáng)的密碼登錄他會(huì)提示我們修改默認(rèn)密碼宾濒,如圖1腿短,它默認(rèn)有驗(yàn)證碼輸入:
還有一種情況我們登錄成功,在登錄的情況下,我們也有修改密碼的入口橘忱,這時(shí)候如圖2赴魁,他沒有要求輸入驗(yàn)證碼
這個(gè)例子很小但也可以利用來體現(xiàn)整個(gè)設(shè)計(jì)過程圖3
我們考慮這個(gè)功能的時(shí)候我們需要考慮這個(gè)功能的全面性,我們公共組件如果只是公共功能的話钝诚,整個(gè)組件我也就能實(shí)現(xiàn)類似于圖2的狀態(tài)尚粘,但我們需要驗(yàn)證碼的時(shí)候,調(diào)用公共組件的時(shí)候敲长,我無論如何也顯示不出來郎嫁,沒有集成在公共組件里怎么實(shí)現(xiàn)?所以公共組件是包含了你分析了在不同地方引用可能出現(xiàn)的所有功能點(diǎn)祈噪,他是所有功能點(diǎn)的并集泽铛。就像一個(gè)商店賣東西,每個(gè)人去買東西買的可能不一樣辑鲤,但每個(gè)人買的時(shí)候盔腔,你盡可能保證有每個(gè)人需要的那個(gè)東西,公共組件也是這樣月褥,我只要用這個(gè)功能你一定得有弛随。扯著扯著感覺好像再說框架一樣,每個(gè)框架引用的時(shí)候你可能只用一部分API宁赤,但他有很多API舀透,你不必?fù)?dān)心,總有人能用到决左。
這樣我們分析了這樣一個(gè)小小的組件愕够,接下來我們該考慮如何實(shí)現(xiàn)它:接下來我們用vue舉例,其實(shí)react等其它框架是一樣的:
驗(yàn)證碼顯示隱藏佛猛,我們肯定想到利用v-if或v-show去控制惑芭,這里我們因?yàn)闆]有頻繁的顯示隱藏,所以為了降低初始開銷继找,我們用v-if
<div class="auth-code" v-if="authCodeShow">
<p class="form-label">
<span class="form-label-name">驗(yàn)證碼:</span>
</p>
<input type="text" v-model="confirmCode">
<img id="img-rand-code" ref="codeImg" :src="codeUrl" @click="changeCode" alt="驗(yàn)證碼">
</div>
通過變量authCodeShow去控制遂跟,這時(shí)候我們考慮這個(gè)參數(shù)應(yīng)該由誰去控制?因?yàn)樵诟附M件引用公共組件的時(shí)候我們才會(huì)考慮他的顯隱婴渡,例如在登錄頁顯示驗(yàn)證碼幻锁,在登陸后不顯示,這樣我們明白他的顯隱是由父組件控制的缩搅,所以這個(gè)應(yīng)該是父組件利用props傳進(jìn)來的:
props: {
authCodeShow: {
type: Boolean,
default: false
}
}
接下來我們還應(yīng)該考慮這個(gè)彈框的顯示隱藏越败,我們肯定是通過父組件中的修改密碼按鈕觸發(fā)的,所以我們控制整個(gè)組件顯示隱藏的也在父組件我們用isShow去控制硼瓣,
// 父組件中調(diào)用子組件
<changePassPop ref="changePass" v-if="popShow"></changePassPop>
我們根據(jù)圖4可以看出我們可以通過彈框中的X 和取消究飞,確定按鈕去關(guān)閉彈窗置谦,自己關(guān)閉自己但我們應(yīng)該仔細(xì)想想,控制子組件的顯示隱藏的參數(shù)在父組件亿傅,所以操作的時(shí)候我們需要同時(shí)改變父組件的值媒峡,所以同時(shí)我們還應(yīng)通過props傳進(jìn)一個(gè)值isShow用來控制彈框的顯示隱藏
<changePassPop ref="changePass" v-if="popShow" :isShow.sync="popShow"></changePassPop>
這里我用了sync修飾符,他的功能其實(shí)就是函數(shù)回調(diào)更改父組件的值葵擎,并不是數(shù)據(jù)的雙向綁定(單項(xiàng)數(shù)據(jù)流也不允許)谅阿,sync的用法這應(yīng)該都知道,不知道的可以穿越 sync修飾符的講解
因?yàn)樵诶永镂矣昧薳lement-ui中的dialog酬滤,所以他還需要一個(gè)show控制dialog的顯示隱藏签餐,模板是這樣
<template>
<div>
<div class="model">
<el-dialog :visible.sync="show" :modal="false" size="tiny" top="20%">
<div class="form">
<div>
<p class="form-label">
<span class="form-label-name">用戶名:</span>
</p>{{username ? username : userName}}</div>
<div>
<p class="form-label">
<span class="form-label-name">舊密碼:</span>
</p>
<input type="password" v-model="oldPass">
</div>
<div>
<p class="form-label">
<span class="form-label-name">新密碼:</span>
</p>
<input type="password" v-model="newPass">
</div>
<div>
<p class="form-label">
<span class="form-label-name">確認(rèn)新密碼:</span>
</p>
<input type="password" v-model="confirmNewPass">
</div>
<div class="auth-code" v-if="authCodeShow">
<p class="form-label">
<span class="form-label-name">驗(yàn)證碼:</span>
</p>
<input type="text" v-model="confirmCode">
<img id="img-rand-code" ref="codeImg" :src="codeUrl" @click="changeCode" alt="驗(yàn)證碼">
</div>
<div slot="footer" class="foot clearFix">
<el-button @click="show=false">取 消</el-button>
<el-button type="primary" @click="changeDefaultPsw">確 定</el-button>
</div>
</div>
</el-dialog>
</div>
</div>
</template>
所以在控制show的時(shí)候同時(shí)改變父組件中的popShow的值,這里我們很容易聯(lián)想到watch方法
watch: {
show() {
this.$emit('update:isShow', false)
}
},
這樣我們子組件控制了自己的顯示隱藏盯串。
接下來我們繼續(xù)看功能氯檐,用戶名是智能添加不是自己輸入那種,其他的舊密碼和新密碼等都是自己輸入的体捏,存在于公共組件的自己的data對象里面冠摄,接下來我們可能很快想到Vuex狀態(tài)管理進(jìn)行數(shù)據(jù)共享,這樣是沒問題的几缭,初始化state河泳,然后通過狀態(tài)管理。這種方法可行年栓,但是單純的一個(gè)純父子組件的數(shù)據(jù)傳遞我感覺沒有必要用狀態(tài)管理拆挥,雖然我當(dāng)初也是利用vuex做的,但我們應(yīng)該明白我們利用Vuex我們是為了更好的追蹤共享數(shù)據(jù)的變化韵洋。講到這里有必要講講vuex了竿刁。有的人用了vuex黄锤,感覺還不錯(cuò)搪缨,就好像全局變量存儲(chǔ)庫,我不用認(rèn)真去考慮數(shù)據(jù)的流向props,這種想法是錯(cuò)的鸵熟!我們不能將vuex當(dāng)成localStorage和sessionStorage去使用副编,vuex是為了更好地去追蹤共享state的變化,為了避免更繁雜的數(shù)據(jù)流向向下圖這樣:
當(dāng)子組件1想想向組件2中進(jìn)行傳值流强,鑒于數(shù)據(jù)的單向流動(dòng)痹届,我們需要經(jīng)過父組件(當(dāng)然是通過函數(shù)回調(diào)傳的),然后在傳給組件2打月,當(dāng)然感覺這樣也還沒什么队腐,若果他們的父組件也是兄弟組件,他們有共同的爺爺奏篙,你需要傳給他的爺爺柴淘,再通過他們爺爺往父組件二進(jìn)行傳遞迫淹,如果再嵌套深一點(diǎn),這TM還不把自己傳亂
所以這時(shí)候vuex就能很好地解決我們的問題为严,共享state也能更好的進(jìn)行追蹤敛熬,同時(shí)更改state只能通過mutation進(jìn)行更改,這樣有了錯(cuò)誤也能更的追蹤第股,當(dāng)項(xiàng)目大的時(shí)候我們可以好好利用vuex的優(yōu)點(diǎn)应民,但是我們知道每次加載頁面的時(shí)候都要初始化store,若果數(shù)據(jù)太多的時(shí)候夕吻,初始化的時(shí)間就比較長诲锹,這樣就會(huì)給人造成一種加載速度慢的印象,所以我們說沒有必要放在vuex的數(shù)據(jù)就不要放涉馅,千萬不能把它當(dāng)成本地存儲(chǔ)辕狰,不要浪費(fèi)了作者的設(shè)計(jì)靈魂。
跑偏了控漠,趕緊回來蔓倍。剛才那個(gè)用戶名的我們其實(shí)存在父組件,接著我們再往下看盐捷,確定按鈕肯定把你的表單提交給后臺(tái)偶翅,但這時(shí)我們又有這樣的顧慮,其實(shí)也真的是顧慮碉渡。我們提交的后臺(tái)接口不一樣聚谁,擦!后臺(tái)這是腫么了滞诺,砍了他形导。哈哈!有時(shí)候這是一個(gè)假設(shè)习霹,但沒準(zhǔn)也可能遇到朵耕,所以確定按鈕綁定的方法我們不能寫死,因?yàn)榻涌诓灰粯勇锪芤叮∷晕覀兛梢赃@樣給公共組件開放個(gè)方法阎曹,confirmChangePsw,我們讓它綁定父組件的方法,提交的方法在父組件實(shí)現(xiàn)因?yàn)楦附M件是可控的煞檩,公共組件只是為了更大的兼容所有的功能要求处嫌,這樣我們在公共組件中這樣定義
// 公共組件
<el-button type="primary" @click="changeDefaultPsw">確 定</el-button>
// 修改默認(rèn)密碼
changeDefaultPsw() {
this.result = this.checkInput('oldPass') && this.checkInput('newPass') &&
this.checkInput('confirmNewPass')
if (this.authCodeShow) {
this.result = this.result && this.checkInput('confirmCode')
}
if (this.result) {
this.show = false;
this.$emit('confirmChangePsw')
}
}
// 父組件調(diào)用公共組件
<changePassPop ref="changePass" v-if="popShow" :isShow.sync="popShow" @confirmChangePsw="handleChangePsw"></changePassPop>
我們通過點(diǎn)擊確定按鈕觸發(fā)了changeDefaultPsw函數(shù),但是這個(gè)方法其實(shí)是為了觸發(fā)子組件綁定的自定義方法confirmChangePsw斟湃,自定義confirmChangePsw方法其實(shí)調(diào)用的是父組件的handleChangePsw方法
可能這時(shí)我們開始懷疑為啥這樣做熏迹?組件自定義方法什么鬼?接下來我們來說說組件的一些特質(zhì)凝赛,請?jiān)徫疫@個(gè)愛引申的毛病
組件與組件之間是相互獨(dú)立的注暗,組件中的方法只能在本組件調(diào)用厨剪,數(shù)據(jù)只能通過單項(xiàng)數(shù)據(jù)流傳遞公共組件在父組件調(diào)用的時(shí)候我們的公共組件訪問的數(shù)據(jù)和方法都是父組件里面的
// 父組件調(diào)用公共組件
<changePassPop ref="changePass" v-if="popShow" :isShow.sync="popShow" @confirmChangePsw="handleChangePsw"></changePassPop>
其實(shí)就類似于函數(shù)的私有作用域
function father(params) {
function son1(params) {
var a = 1
}
function son2(params) {
console.log(a); // 調(diào)用的時(shí)候會(huì)報(bào)錯(cuò)
}
}
因?yàn)楹瘮?shù)會(huì)形成一個(gè)私有作用域不允許其他無關(guān)函數(shù)訪問,有人可能會(huì)說這樣不就行了
function father(params) {
var a = 1
function son1(params) {
}
function son2(params) {
console.log(a); // 1
}
}
對呀友存!像上面這樣是沒問題的祷膳!所以剛才我說的是組件就類似于函數(shù)的私有作用域,類似屡立,類似V背俊!但不完全一樣膨俐,組件有著更嚴(yán)格的權(quán)限勇皇,子組件也不能共享父組件的數(shù)據(jù),如果你需要父組件的某個(gè)數(shù)據(jù)只能通過props傳遞過來
繼續(xù)回來焚刺。敛摘。。乳愉。
我們相當(dāng)于給確定按鈕一個(gè)開放的權(quán)限兄淫,我給你暴露的方法,但如何定義由你自己控制蔓姚。
當(dāng)然還有一些ref拿值的方法捕虽!自己看代碼去體會(huì)吧!
我貼下代碼
<template>
<div>
<div class="model">
<el-dialog :visible.sync="show" :modal="false" size="tiny" top="20%">
<div class="form">
<div>
<p class="form-label">
<span class="form-label-name">用戶名:</span>
</p>{{username ? username : userName}}</div>
<div>
<p class="form-label">
<span class="form-label-name">舊密碼:</span>
</p>
<input type="password" v-model="oldPass">
</div>
<div>
<p class="form-label">
<span class="form-label-name">新密碼:</span>
</p>
<input type="password" v-model="newPass">
</div>
<div>
<p class="form-label">
<span class="form-label-name">確認(rèn)新密碼:</span>
</p>
<input type="password" v-model="confirmNewPass">
</div>
<div class="auth-code" v-if="authCodeShow">
<p class="form-label">
<span class="form-label-name">驗(yàn)證碼:</span>
</p>
<input type="text" v-model="confirmCode">
<img id="img-rand-code" ref="codeImg" :src="codeUrl" @click="changeCode" alt="驗(yàn)證碼">
</div>
<div slot="footer" class="foot clearFix">
<el-button @click="show=false">取 消</el-button>
<el-button type="primary" @click="changeDefaultPsw">確 定</el-button>
</div>
</div>
</el-dialog>
</div>
</div>
</template>
<script>
import {
mapState
} from 'vuex'
import {
STI_AJAX_BASEURL
} from 'commons/constant'
const CODE_URL = `${STI_AJAX_BASEURL}/login/vcode`
export default {
props: {
isShow: {
type: Boolean,
default: false
},
authCodeShow: {
type: Boolean,
default: false
},
userName: {
type: String
}
},
computed: {
...mapState({
username: state => state.userInfo.username,
userId: state => state.userInfo.userId
})
},
watch: {
show() {
this.$emit('update:isShow', false)
}
},
mounted() {
if (this.authCodeShow) {
this.changeCode()
}
},
data() {
return {
result: null,
show: true,
oldPass: '',
newPass: '',
confirmNewPass: '',
confirmCode: '',
codeUrl: CODE_URL,
tips: {
oldPass: '請輸入舊密碼',
newPass: '新密碼不能為空',
confirmNewPass: '兩次輸入的密碼不一致',
authcode: '請輸入驗(yàn)證碼'
}
};
},
methods: {
// 檢查輸入內(nèi)容
checkInput(param) {
if (this.$data[param]) {
if (param === 'confirmNewPass') {
if (this.confirmNewPass !== this.newPass) {
alert('兩次輸入的密碼不一致')
}
}
return true
} else {
alert(this.tips[param])
return false
}
},
// 更換驗(yàn)證碼
changeCode() {
this.codeUrl = CODE_URL + '?' + new Date().getTime()
},
// 修改默認(rèn)密碼
changeDefaultPsw() {
this.result = this.checkInput('oldPass') && this.checkInput('newPass') && this.checkInput('confirmNewPass')
if (this.authCodeShow) {
this.result = this.result && this.checkInput('confirmCode')
}
if (this.result) {
this.show = false;
this.$emit('confirmChangePsw')
}
}
}
}
</script>
<style lang="less" scoped>
.clearFix:after {
display: block;
height: 0;
content: '';
clear: both;
overflow: hidden;
}
.form {
>div {
height: 40px;
margin-bottom: 6px;
>input {
height: 36px;
width: 60%;
line-height: 36px;
padding-left: 8px;
}
.form-label {
float: left;
width: 100px;
height: 38px;
margin-right: 20px;
.form-label-name {
float: right;
}
}
}
.foot {
width: 100%;
margin-top: 20px;
padding-left: 56%;
}
}
</style>
通過上面的分析我們可以歸納到公共組件的抽象方法
- 先分析我可能涵蓋的功能需求
- 需要傳哪些值進(jìn)來
- 需要開放哪些方法便于調(diào)用該組件的組件自己定義靈活控制