Vue3造轮子(二)-Button组件

效果预览

代码链接

提交历史

API设计

需求

  • 支持button的基本事件 — click, focus,mouseover等 — 属性绑定

  • 可以是基础按钮,可以是链接 — theme

  • 可以有不同的等级 — level

  • 可以改变大小 — size

  • 可以禁用 — diabled

  • 加载状态 — loading

用户怎么用该组件

1
2
3
4
5
6
7
8
9
10
11
<x-button 
@click=?
@focus=?
@mouseover=?
theme="default or primary or link"
level="nomal or info or warning or success or danger"
size="mini or small or normal or large"
disabled
loading
></x-button>

Vue3笔记

属性绑定

非Prop 属性继承

参考文档

  • 如果父组件传给子组件的属性,子组件中没有相应 propsemits定义,则这些属性被称为非Prop属性
  • 当组件返回单个根节点时,Vue3会默认将所有非 prop属性绑定到子组件的根元素。

基于此特性,父组件中绑定的事件可以直接传至子组件,因此可以初步实现button组件对事件的支持,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 父组件
<template>
<x-button @click="clickButton"
@focus="clickButton"
@mouseover="clickButton">
点我
</x-button>
</template>
<script lang="ts">
import XButton from "../lib/Button.vue";
export default {
components: { Button },
setup(){
const clickButton = ()=>{
console.log(1)
}
return {clickButton}
}
};
</script>
1
2
3
4
5
6
7
8
<!-- button 组件 -->
<template>
<div class="x-button-dv">
<button class="x-button">
<slot></slot>
</button>
</div>
</template>

禁用属性继承

从上面的代码中,不难发现一个问题——button组件的事件其实应该绑定给<button/>元素而不是其根元素;而事件之外的其他属性需绑定给根元素。解决这些问题的思路如下:

  • 通过将 inheritAttrs 选项设置为 false,禁用非prop属性的继承
  • 获取传来的属性,通过v-bind将事件绑定给<button/>元素
  • 将其余属性绑定给根元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div class="x-button-dv" :size="size">
<button class="x-button" v-bind="rest">
<slot></slot>
</button>
</div>
</template>

<script>
export default {
name: 'x-button',
inheritAttrs: false,
setup (props, context) {
const { size, ...rest } = context.attrs // 将事件绑定给button,其余属性绑定给外层div
return { size, rest }
}
}
</script>

props V.S. attrs

  • props要先声明才能取值,attrs不用声明
  • 当props声明了属性时,该属性不能在attrs里面取到
  • props不包含事件,attrs包含
  • 当属性的数据类型为Booalen时,必须要给该属性绑定value,否则attrs中为空字符串
1
2
3
4
5
6
7
8
9
// 父组件
<x-button size="small"
disabled
:multiple="true"
@click="clickButton"
@mouseenter="clickButton"
@focus="clickButton">
点我
</x-button>
1
2
3
4
5
6
7
8
9
10
11
12
// 子组件
props: {
size: {
type: String
}
},
setup (props, context) {
console.log( {...props})
console.log( {...context.attrs})
const { size, ...rest } = context.attrs // 将事件绑定给button,其余属性绑定给外层div
return { size, rest }
}

输出结果

补充

css影响最小原则

CSS 绝对不能影响库使用者

  • 尽量不使用 scoped
    因为 data-v-xxx 中的 xxx 每次运行可能不同
    必须输出稳定不变的 class 选择器,方便使用者覆盖
  • 必须加前缀,不容易被使用者覆盖

css写loading动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.xx-loadingIndicator{
$main-theme-color: #87dfd6;
width: 14px;
height: 14px;
display: inline-block;
margin-right: 4px;
border-radius: 14px;
border-color: $main-theme-color $main-theme-color $main-theme-color transparent;
border-style: solid;
border-width: 2px;
animation: xx-spin 1s infinite linear;
}

@keyframes xx-spin {
0% { transform: rotate(0deg) }
100% { transform: rotate(360deg) }
}

总结

  • Vue 属性继承
    • 默认属性传给根元素
    • inheritAttrs: false 禁用属性继承
    • v-bind="$attrs"绑定属性, context.attrs获取属性
  • props V.S. attrs