这篇文章紧接的上一篇文章,上篇文章是对给element的button组件写了一个单元测试,这篇文章是使用vue3的语法进行重构,这里说一下单元测试和重构的联系,当你给组件写了单元测试之后,重构会减少你很多的debug时间,磨刀不误砍柴工,这里推荐大家先从上一篇文章看起:https://www.jianshu.com/p/01d2860607c1
vue3.0发布,大家是不是很兴奋呢!又可以拿头发换钱了,唉!命苦。
人如其名,本文的主题就是使用vue3的语法重构element的button组件
上才艺!
先来看一下element的button.vue本身是什么样子
<template>
<button
class="el-button"
@click="handleClick"
:disabled="buttonDisabled || loading"
:autofocus="autofocus"
:type="nativeType"
:class="[
type ? 'el-button--' + type : '',
buttonSize ? 'el-button--' + buttonSize : '',
{
'is-disabled': buttonDisabled,
'is-loading': loading,
'is-plain': plain,
'is-round': round,
'is-circle': circle,
},
]"
>
<i class="el-icon-loading" v-if="loading"></i>
<i :class="icon" v-if="icon && !loading"></i>
<span v-if="$slots.default"><slot></slot></span>
</button>
</template>
<script>
export default {
name: 'ElButton',
inject: {
elForm: {
default: '',
},
elFormItem: {
default: '',
},
},
props: {
type: {
type: String,
default: 'default',
},
size: String,
icon: {
type: String,
default: '',
},
nativeType: {
type: String,
default: 'button',
},
loading: Boolean,
disabled: Boolean,
plain: Boolean,
autofocus: Boolean,
round: Boolean,
circle: Boolean,
},
computed: {
_elFormItemSize() {
return (this.elFormItem || {}).elFormItemSize;
},
buttonSize() {
return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
},
buttonDisabled() {
return this.disabled || (this.elForm || {}).disabled;
},
},
methods: {
handleClick(evt) {
this.$emit('clicked', evt);
},
},
};
</script>
重构思路:我们先一步步把每个方法、每个属性分解开,先挑软柿子捏,一步步的把他按照vue3的Composition API手法进行重构。
重构handleClick方法
重构前
<script>
export default{
methods: {
handleClick(evt) {
this.$emit('clicked', evt);
},
},
}
</script>
重构后
<script>
export default{
setup(props, {emit}) {
const handleClick = (evt) => {
emit('clicked', evt);
};
return {
handleClick,
};
},
}
</script>
重构buttonDisabled属性
重构前
<script>
export default{
computed: {
buttonDisabled() {
return this.disabled || (this.elForm || {}).disabled;
},
},
}
</script>
重构后
<script>
const usebuttonDisabled = (disabled) => {
const elForm = inject('elForm', {});
const buttonDisabled = computed(() => {
return disabled.value || elForm.disabled;
});
return buttonDisabled;
};
export default{
setup(props, {emit}) {
const {disabled} = toRefs(props);
const buttonDisabled = usebuttonDisabled(disabled);
return {
buttonDisabled,
};
},
}
</script>
这边说一下重构buttonDisabled这个属性的时候,遇到的一些坑。
1.解构props里面的值的时候,会使这个属性失去响应式,从而导致页面报错。
如果我在解构props的disabled属性的时候这样写
const {disabled} = props;
那么就会报如下错误:
大概翻译下就是 :
从' setup() '根作用域中的' props '获取值将导致该值失去反应性。
所以需要我们用一个toRefs()这样一个api来解决这个问题
const {disabled} = toRefs(props);
toRefs()这个方法可以使一个对象变成响应式对象
重构buttonSize属性
重构前
<script>
export default{
computed: {
buttonSize() {
return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
},
},
}
</script>
重构后
<script>
const useButtonSize = (size) => {
const elFormItem = inject('elFormItem', {});
const _elFormItemSize = computed(() => {
return elFormItem.elFormItemSize;
});
const buttonSize = computed(() => {
return (
size.value ||
_elFormItemSize.value ||
(getCurrentInstance().ctx || {})._elFormItemSize
);
});
return buttonSize;
};
setup(props, {emit}) {
const {size} = toRefs(props);
const buttonSize = useButtonSize(size)
return {
buttonSize
};
},
</script>
注意:
computed返回的是一个ref对象,所以在使用的时候要用.value
遇到的坑:
当props中,如果某个属性没被给默认值也就是没有分配内存空间,就转换不成ref对象
如果我的props的值是这样,因为解构会使props值失去响应式,所以我想用toRefs(),转化成响应式对象,
<script>
props:{
size:String
}
setup(props){
const {size} = toRefs(props);
console.log('-----------------',size)
}
</script>
当我们输出size一看,发现是undefined,what?
当我们给size设置一个默认值""
<script>
props:{
size:{
type:String,
default:''
}
}
setup(props){
const {size} = toRefs(props);
console.log('-----------------',size)
}
</script>
输出size的时候,就是一个ref对象
如此,全部重构完毕;
贴一份重构完成的代码:
<template>
<button
class="el-button"
@click="handleClick"
:disabled="buttonDisabled || loading"
:autofocus="autofocus"
:type="nativeType"
:class="[
type ? 'el-button--' + type : '',
buttonSize ? 'el-button--' + buttonSize : '',
{
'is-disabled': buttonDisabled,
'is-loading': loading,
'is-plain': plain,
'is-round': round,
'is-circle': circle,
},
]"
>
<i class="el-icon-loading" v-if="loading"></i>
<i :class="icon" v-if="icon && !loading"></i>
<span v-if="$slots.default"><slot></slot></span>
</button>
</template>
<script>
import {computed, getCurrentInstance, inject, toRefs, unref} from 'vue';
const usebuttonSize = (size) => {
const elFormItem = inject('elFormItem', {});
const _elFormItemSize = computed(() => {
return unref(elFormItem.elFormItemSize);
});
const buttonSize = computed(() => {
return (
size.value ||
_elFormItemSize.value ||
getCurrentInstance().ctx._elFormItemSize
);
});
return buttonSize;
};
const userButtonDisabled = (disabled) => {
const elForm = inject('elForm', {});
const buttonDisabled = computed(() => {
return disabled.value || elForm.disabled;
});
return buttonDisabled;
};
export default {
name: 'ElButton',
inject: {
elForm: {
default: '',
},
elFormItem: {
default: '',
},
},
props: {
type: {
type: String,
default: 'default',
},
size: {
type: String,
default: '',
},
icon: {
type: String,
default: '',
},
nativeType: {
type: String,
default: 'button',
},
loading: Boolean,
disabled: Boolean,
plain: Boolean,
autofocus: Boolean,
round: Boolean,
circle: Boolean,
},
computed: {
_elFormItemSize() {
return (this.elFormItem || {}).elFormItemSize;
},
// buttonSize() {
// return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
// },
// buttonDisabled() {
// return this.disabled || (this.elForm || {}).disabled;
// },
},
methods: {
// handleClick(evt) {
// this.$emit('clicked', evt);
// },
},
setup(props, {emit}) {
const handleClick = (evt) => {
emit('clicked', evt);
};
const {disabled, size} = toRefs(props);
// 重构butonDisabled属性
const buttonDisabled = userButtonDisabled(disabled);
// 重构buttonSize属性
const buttonSize = usebuttonSize(size);
return {
handleClick,
buttonDisabled,
buttonSize,
};
},
};
</script>
喜欢的朋友记得点赞、收藏、关注哦!!!