前言
大多數前端框架都提供了組件化的能力,在日常開發中,我們經常會用組件去封裝自己的業務邏輯,那麼如何去寫一個複用率高的組件就變成了一個重要的技巧。這裏總結一下在使用Vue時幾種複用組件的方式。
場景
表單驗證是一個經常碰到的場景,表單內的不同組件常常需要有相同的驗證邏輯,比如驗證值非空,這裏就按照這個場景來舉幾個例子。
使用Mixin
Vue提供了 “混入” 這個方案來達到一個複用的效果,所以我們可以寫一個表單校驗相關的mixin,並且混入到各個組件中去。
// validate-mixin.js
const validateMixin = {
props: {
required: {
type: Boolean,
default: false
}
},
methods: {
validate(value) {
if (this.required && (value === undefined || value === null || value === '')) {
this.isError = true;
} else {
this.isError = false;
}
}
}
};
export default validateMixin
然後我這邊有兩個組件,分別是文本輸入框和數字輸入框
// input.js
<template>
<div id="ka-input">
<input type="text" @input="onInput">
<p v-show="isError" class="err-msg"> {{errMsg}} </p>
</div>
</template>
<script>
import validateMixin from '@/common/mixin/validate-mixin.js'
export default {
name: 'KaInput',
mixins: [validateMixin],
methods: {
onInput(e) {
this.validate(e.target.value);
}
},
data() {
return {
isError: false,
errMsg: '不能爲空'
};
}
};
</script>
// number-input.js
<template>
<div id="ka-number-input">
<input type="number" @input="onInput">
<p v-show="isError" class="err-msg"> {{errMsg}} </p>
</div>
</template>
<script>
import validateMixin from '@/common/mixin/validate-mixin.js'
export default {
name: 'KaNumberInput',
mixins: [validateMixin],
methods: {
onInput(e) {
this.validate(e.target.value);
}
},
data() {
return {
isError: false,
errMsg: '不能爲空'
};
}
};
</script>
混入的優點在於使用簡單,將共同的屬性或者方法封裝在這個mixin對象之中,其他需要共享該屬性或者方法的組件只要引入這個mixin即可。
但是從上述代碼中可以看出,混入有幾個明顯的缺點。
- 模板的內容需要維護在各個組件當中,得不到複用。
- 屬性和方法放在mixin中,後期不方便他人維護,需要手動梳理源組件和混入對象之間的邏輯。
- mixins會導致屬性衝突,並且相同名稱的生命週期會被合併,這可能帶來冗餘的邏輯,同時也可以看出mixin的侵入性較強。
實際上在開發過程中,如何組織mixin的目錄結構也是個問題,要區分是一個頁面級別的mixin,還是部分頁面的mixin,甚至是全站的mixin,劃分不清楚會導致後期mixin的職能混亂,不利於代碼維護。
RenderLess
renderLess 本意就是不渲染內容的組件,在Vue 中我們可以使用插槽來實現,和React中的HOC有異曲同工之妙。
// validate-render-less.vue
<template>
<div class="validate-item">
<slot name="wrapped" :validate="validate"></slot>
<p v-show="isError" class="err-msg">{{ errMsg }}</p>
</div>
</template>
<script>
export default {
data: () => {
return {
isError: false,
errMsg: 'can not be null'
};
},
props: {
required: {
type: Boolean,
required: false
}
},
methods: {
validate(e) {
const value = e.target.value;
if (
this.required &&
(value === undefined || value === null || value === '')
) {
this.isError = true;
} else {
this.isError = false;
}
}
}
};
</script>
上面這個模塊本質上是一個vue組件,他有模板,有邏輯,並且利用插槽在模板中預留了一個“佔位符”,其他組件可以往這裏添加自己的內容。
// input.js
<template>
<div id="ka-input">
<r-l-validate :required="required">
<template v-slot:wrapped="{ validate }">
<input type="text" @input="validate($event)" />
</template>
</r-l-validate>
</div>
</template>
<script>
import validate from '@/common/render-less/validate.vue';
const kaInput = {
props: {
required: {
type: Boolean
}
},
name: 'ka-input',
components: {
'r-l-validate': validate
}
};
export default kaInput;
</script>
// number-input.js
<template>
<div id="ka-number-input">
<r-l-validate :required="required">
<template v-slot:wrapped="{ validate }">
<input type="number" @input="validate($event)">
</template>
</r-l-validate>
</div>
</template>
<script>
import validate from '@/common/render-less/validate.vue'
export default {
name: 'KaNumberInput',
props: {
required: {
type: Boolean,
required: false
}
},
components: {
'r-l-validate': validate
}
};
</script>
使用render-less組件有這麼幾個優點。
- 可以複用模板的邏輯,公共的部分都可以提取到renderless中
- 不會出現命名衝突
簡單總結
對於簡單的場景,mixin可以解決大部分問題,但是要注意他的一些缺點;其他的場景下,如果需要複用模板,並且組件的邏輯較爲複雜,那應該優先考慮使用render-less組件