Button组件
需求分析
-
可以有不同的等级(level)
-
可以是链接,可以是文字
-
可以click、focus、鼠标悬浮
-
可以改变size: 大中小
-
可以禁用(disabled)
-
可以加载中(loading)
API设计
- Button组件怎么用
<Button
@click=?
@focus=?
@mouseover=?
theme="button or link or text"
level="main or normal or minor"
size="big normal small"
disabled
loading
></Button>
Vue 3 属性绑定
让Button支持事件(@click @focus @mouseover)
在使用组件的时候,传入的事件,会默认传给组件的最外层的标签上,例如:如果button组件的最外层是<button><button>标签,那么传入的事件就会传给button标签。如果button组件是<div> <button> </button> </div>标签,那么传入的事件就会传给div标签。
让div不继承属性
用inheritAttrs: false,让div不继承属性。
<template>s
<div>
<button>
<slot/>
</button>
</div>
</template>
<script lang="ts">
export default {
inheritAttrs: false //继承属性改为false,就是取消绑定
}
</script>
如果想让div里面的button绑定属性,可以用v-bind=$attrs,就是这个对象的所有属性都要绑定到button上面
<template>
<div>
<button v-bind="$attrs">
<slot />
</button>
</div>
</template>
<script lang="ts">
export default {
inheritAttrs: false
}
</script>
想让一部分事件出现在div上,一部分出现在button上
<template>
<div :size="size"> //div上绑定size事件
<button v-bind="rest"> //button上绑定除size以外的事件
<slot />
</button>
</div>
</template>
<script lang="ts">
export default {
inheritAttrs: false ,
setup(props, context){
const {size, ...rest} = context.attrs
return {size, rest}
}
}
</script>
...rest是ES6语法(剩余操作符)的小技巧
总结
-
默认所有属性都绑定到根元素
-
使用
inheritattrs: false可以取消默认绑定 -
使用
$attrs或者context.attrs获取所有属性 -
使用
v-bind="$attrs"批量绑定属性 -
使用
const {size, level, ...xxx} = context.attrs将属性分开
props与attrs的区别
-
props需要先声明才能获取值,而attrs则不用
-
props声明过的属性,attrs里面不会再出现
-
props不包含事件,attrs包含
-
props支持string以外的类型,而attrs只有string类型
让button支持theme属性
theme 的值为 button / link / text
Button.vue
<template>
<button class="circle-button" :class="{ [`theme-${theme}`]: theme }">
<slot />
</button>
</template>
<script lang="ts">
export default {
props: {
theme: {
type: String,
default: "button",
},
},
};
ButtonDemo.vue
<template>
<div>Button 示例</div>
<h1>示例1</h1>
<div>
<Button>你好</Button>
<Button theme="button">你好</Button>
<Button theme="link">你好</Button>
<Button theme="text">你好</Button>
</div>
</template>
<script lang="ts">
import Button from "../lib/Button.vue";
export default {
components: { Button },
setup() {
const onClick = () => {
console.log("hi");
};
return { onClick };
},
};
</script>
UI库的CSS注意事项
不能使用scoped
因为data-v-xxx中的xxx每次运行可能不同
必须输出稳定不变的class选择器,方便使用者覆盖
每个CSS类必须加前缀
.button 不行,很容易被使用者覆盖
.ciecle-button 可以,不太容易被覆盖
.theme-link 不行,很容易被使用者覆盖
.circle-theme-link 可以,不太容易被覆盖
注意:CSS最小影响原则(CSS绝对不能影响库使用者
button支持的属性
button 支持 size 属性
size 的值为 big / normal / small
button 支持 level 属性
main / normal / danger
button 支持 disabled 属性
disabled的值为 true/false
<button disabled>
<button :disabled="true">
<button disabled="true">
<button disabled= "false">
让 button 支持 loading 属性
loading 值为 true / false
代码
Button.vue
<template>
<button class="circle-button" :class="classes" :disabled="disabled">
<span v-if="loading" class="circle-loadingIndicator"></span>
<slot />
</button>
</template>
<script lang="ts">
import { computed } from "vue";
export default {
props: {
theme: {
type: String,
default: "button",
},
size: {
type: String,
default: "normal",
},
level: {
type: String,
default: "normal",
},
disabled: {
type: Boolean,
default: false,
},
loading: {
type: Boolean,
default: false,
},
},
setup(props) {
const { theme, size, level } = props;
const classes = computed(() => {
return {
[`circle-theme-${theme}`]: theme,
[`circle-size-${size}`]: size,
[`circle-level-${level}`]: level,
};
});
return { classes };
},
};
</script>
<style lang="scss">
$h: 32px;
$border-color: #d9d9d9;
$color: #333;
$blue: #40a9ff;
$radius: 4px;
$red: red;
$grey: grey;
.circle-button {
box-sizing: border-box;
height: $h;
padding: 0 12px;
cursor: pointer;
display: inline-flex;
justify-content: center;
align-content: center;
white-space: nowrap;
background: white;
color: $color;
border: 1px solid $border-color;
border-radius: $radius;
box-shadow: 0 1px 0 fade-out(black, 0.95);
transition: background 250ms;
& + & {
margin-left: 8px;
}
&:hover,
&:focus {
color: $blue;
border-color: $blue;
}
&:focus {
outline: none;
}
&::-moz-focus-inner {
border: 0;
}
&.circle-theme-link {
border-color: transparent;
box-shadow: none;
color: $blue;
&.hover,
&:focus {
color: lighten($blue, 10%);
}
}
&.circle-theme-text {
border-color: transparent;
box-shadow: none;
color: inherit;
&:hover,
&:focus {
background: darken(white, 5%);
}
}
&.circle-size-big {
font-size: 24px;
height: 48px;
padding: 0 16px;
}
&.circle-size-small {
font-size: 12px;
height: 20px;
padding: 0 4px;
}
&.circle-theme-button {
&.circle-level-main {
background: $blue;
color: white;
border-color: $blue;
&:hover,
&:focus {
background: darken(white, 5%);
border-color: darken($blue, 10%);
}
}
&.circle-level-danger {
background: $red;
border-color: $red;
color: white;
&:hover,
&:focus {
background: darken($red, 10%);
border-color: darken($red, 10%);
}
}
}
&.circle-theme-link {
&.circle-level-danger {
color: $red;
&.hover,
&.focus {
color: darken($red, 10%);
}
}
}
&.circle-theme-text {
&.circle-level-main {
color: $blue;
&:hover,
&:focus {
color: darken($blue, 10%);
}
}
&.circle-level-danger {
color: $red;
&.hover,
&.focus {
color: darken($red, 10%);
}
}
}
&.circle-theme-button {
&[disabled] {
cursor: not-allowed;
color: $grey;
&:hover {
border-color: $grey;
}
}
}
&.circle-theme-link,
&.circle-theme-text {
&[disabled] {
cursor: not-allowed;
color: $grey;
}
}
> .circle-loadingIndicator {
width: 14px;
height: 14px;
display: inline-block;
margin-right: 4px;
border-radius: 8px;
border-color: $blue $blue $blue transparent;
border-style: solid;
border-width: 2px;
animation: circle-spin 1s infinite linear;
}
}
@keyframes circle-spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
ButtonDemo.vue
<template>
<div>Button 示例</div>
<h1>示例1</h1>
<div>
<Button>你好</Button>
<Button theme="button">你好</Button>
<Button theme="link">你好</Button>
<Button theme="text">你好</Button>
</div>
<h1>示例2</h1>
<div>
<div>
<Button size="big">大大大</Button>
<Button>普通</Button>
<Button size="small">小小小</Button>
</div>
<div>
<Button theme="link" size="big">大大大</Button>
<Button theme="link">普通</Button>
<Button theme="link" size="small">小小小</Button>
</div>
<div>
<Button theme="text" size="big">大大大</Button>
<Button theme="text">普通</Button>
<Button theme="text" size="small">小小小</Button>
</div>
</div>
<h1>示例3</h1>
<div>
<div>
<Button level="main">主要按钮</Button>
<Button>普通按钮</Button>
<Button level="danger">危险按钮</Button>
</div>
<div>
<Button theme="link" level="main">主要链接按钮</Button>
<Button theme="link">普通链接按钮</Button>
<Button theme="link" level="danger">危险链接按钮</Button>
</div>
<div>
<Button them e="text" level="main">主要文字按钮</Button>
<Button theme="text">普通文字按钮</Button>
<Button theme="text" level="danger">危险文字按钮</Button>
</div>
</div>
<h1>示例4</h1>
<div>
<Button disabled>禁用按钮</Button>
<Button theme="link" disabled>禁用按钮</Button>
<Button theme="text" disabled>禁用按钮</Button>
</div>
<h1>示例5</h1>
<div>
<Button loading>加载中</Button>
</div>
</template>
<script lang="ts">
import Button from "../lib/Button.vue";
export default {
components: { Button },
setup() {
const onClick = () => {
console.log("hi");
};
return { onClick };
},
};
</script>