Button 按钮

Button 按钮

Button.vue

script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import { ref, computed, inject } from 'vue';
import type { ButtonProps, ButtonEmits, ButtonInstance } from './types';
import { BUTTON_GROUP_CTX_KEY } from './constants';
import { throttle } from 'lodash-es';
import VrIcon from '../Icon/Icon.vue';

defineOptions({
name: 'VrButton',
});

const props = withDefaults(defineProps<ButtonProps>(), {
tag: 'button',
nativeType: 'button',
useThrottle: true,
throttleDuration: 500,
});

const emits = defineEmits<ButtonEmits>();
const slots = defineSlots();
const buttonGroupCtx = inject(BUTTON_GROUP_CTX_KEY, void 0);

const _ref = ref<HTMLElement>();
const size = computed(() => buttonGroupCtx?.size ?? props.size ?? '');
const type = computed(() => buttonGroupCtx?.type ?? props.type ?? '');
const disabled = computed(() => props.disabled || buttonGroupCtx?.disabled || false);
const hasDefaultSlot = computed(() => !!slots.default);
const iconStyle = computed(() => ({
marginRight: hasDefaultSlot.value ? '6px' : '0px',
}));

const handleBtnClick = (e: MouseEvent) => {
emits('click', e);
};

const handleBtnClickThrottle = throttle(
handleBtnClick,
props.throttleDuration
);

defineExpose<ButtonInstance>({
ref: _ref,
disabled,
size,
type,
loading: computed(() => props.loading),
click: handleBtnClick,
});

关键解释

defineOptions

最终导出组件名为 VrButton,在 packages\core\components.ts 中以以下形式导入

1
import {VrButton} from '@veyra/components'

withDefaults

defineProps 绑定默认值

接口

1
2
3
4
5
interface ButtonProps{
tag?: string | Component
size?: 'default' | 'large' | 'small'
...
}
1
2
3
4
5
6
>const props = withDefaults(defineProps<ButtonProps>(), {
tag: 'button',
nativeType: 'button',
useThrottle: true,
throttleDuration: 500,
>})

defineEmits

子向父发射事件

子组件

1
2
3
4
5
6
7
8
9
const emits = defineEmits<ButtonEmits>()

const handleBtnClick = (e: MouseEvent) => {
emits('click', e)
}
const handleBtneCLickThrottle = throttle(
handleBtnClick,
props.throttleDuration
)

父组件 用户使用组件

1
<vr-... @click="Click" /> //当接收到子组件发射过来的事件之后,触发父组件自己的'Click'函数

1.点击后 子组件的 @click 被触发,将一个 MouseEvent 类型事件传入 handleBtnClickhandleBtneCLickThrottle 函数

2.emits('click', e) 向父组件发射 'click' 事件

3.父组件通过@click响应到 'click' 事件, 开始执行父组件中自定义的 Click 事件

defineSlots

1
const slots = defineSlots()
1
2
3
const iconStyle = computed(() => ({
marginRight: slots.default ? '6px' : '0px',
}))

检测组件是否有默认插槽, 有则插入样式 marginRight: 6px

template

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<component
:is="tag"
ref="_ref"
class="vr-button"
:class="{
[`vr-button--${type}`]: type,
[`vr-button--${size}`]: size,
'is-plain': plain,
'is-round': round,
'is-circle': circle,
'is-disabled': disabled,
'is-loading': loading,
}"
:disabled="disabled || loading"
:type="tag === 'button' ? nativeType : void 0"
:autofocus="autofocus"
@click="useThrottle ? handleBtnClickThrottle : handleBtnClick"
>
<template v-if="loading">
<slot name="loading">
<vr-icon
class="loading-icon"
:icon="loadingIcon ?? 'spinner'"
:style="iconStyle"
size="1x"
spin
/>
</slot>
</template>
<vr-icon
v-if="icon && !loading"
:icon="icon"
size="1x"
:style="iconStyle"
/>
<slot></slot>
</component>

关键解释

<component></component>是vue内置的动态组件
通过:is可控制组件的最终类型

例如<component :is="button"></component>的最终渲染结果为<button></button>

ref="_ref"

首先,ref 属性的作用是将 **DOM 元素 **或 子组件实例 绑定 到对应的 响应式变量

ref="_ref" 将渲染的 DOM 元素(例如 <button><a>)绑定到 _ref 变量

组件挂载后,_ref.value 就会指向这个 DOM 元素

使用 defineExpose_ref 暴露给父组件,允许父组件访问子组件的 DOM 元素

例如,父组件可以调用子组件按钮的 focus() 方法

1
2
><vr-button ref="myButton" />
><button @click="focus">focus vr-button</button>
1
2
3
4
5
6
7
>import { ref } from 'vue'

>const myButton = ref(null)

>function focus() {
myButton.value.ref.focus() // 调用子组件的 DOM 元素的 focus 方法
>}

当用户点击父组件的按钮时,子组件的按钮会获得焦点

:class

使用对象语法动态绑定类名

例如文中两种

type 存在时(如 primarysuccess),添加类名 er-button--primary

'is-plain': plain

当 plain 为 true 时,添加类名 is-plain

constants.ts

1
2
3
4
5
6
7
8
import type { InjectionKey } from "vue";
import type { ButtonGroupContext } from "./types";

//注入键(Injection Key)(BUTTON_GROUP_CTX_KEY)
//主要作用是为按钮组组件(`ButtonGroup`)提供上下文(context),使得按钮组中的子按钮可以访问父级按钮组的相关信息。
export const BUTTON_GROUP_CTX_KEY:
InjectionKey<ButtonGroupContext> =
Symbol("buttonGroupContext");//原始数据类型,用于创建唯一的标识符

关键解释

举个例子

1
2
3
4
5
6
7
8
9
10
11
//父组件
//使用 `provide` 方法将某些数据或方法提供给子组件
import { provide } from "vue";
import { BUTTON_GROUP_CTX_KEY } from "./constants";
export default {
setup() {
const buttonGroupContext = { size: "large", type: "primary", };
provide(BUTTON_GROUP_CTX_KEY, buttonGroupContext); // 提供上下文
return {};
},
};
1
2
3
4
5
6
7
8
9
10
11
//子组件
//通过 `inject` 方法获取父组件提供的上下文
import { inject } from "vue";
import { BUTTON_GROUP_CTX_KEY } from "./constants";
export default {
setup() {
const buttonGroupContext = inject(BUTTON_GROUP_CTX_KEY); // 注入上下文
console.log(buttonGroupContext); // 输出 { size: "large", type: "primary" }
return {};
},
};

types.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import { type Component, type ComputedRef, type Ref } from 'vue'
export type ButtonType = 'primary' | 'success' | 'warning' | 'danger' | 'info'
export type NativeType = 'button' | 'submit' | 'reset'
export type ButtonSize = 'default' | 'large' | 'small'

export interface ButtonProps {
tag?: string | Component
type?: ButtonType
size?: ButtonSize
plain?: boolean
round?: boolean
circle?: boolean
disabled?: boolean
autofocus?: boolean
nativeType?: NativeType
icon?: string
loading?: boolean
loadingIcon?: string
useThrottle?: boolean
throttleDuration?: number
}

export interface ButtonGroupProps {
size?: ButtonSize
type?: ButtonType
disabled?: boolean
}

export interface ButtonGroupContext {
size?: ButtonSize
type?: ButtonType
disabled?: boolean
}

export interface ButtonEmits {
(e: 'click', value: MouseEvent): void
}

export interface ButtonInstance {
ref: Ref<HTMLButtonElement | void>
disabled: ComputedRef<boolean>
size: ComputedRef<string>
type: ComputedRef<string>
}