前言
大多数前端框架都提供了组件化的能力,在日常开发中,我们经常会用组件去封装自己的业务逻辑,那么如何去写一个复用率高的组件就变成了一个重要的技巧。这里总结一下在使用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组件