引言
再对Elements-Plus的设计框架有了初步了解过后 也通过对最基础的组件elIcon的设计源码进行分析之后,理解了其中组件设计的流程。那么再继续提高组件的复杂度继续看看elButton组建的实现。 组件目录如下
js
├── packages
│ ├── components
│ │ ├── button
│ │ │ ├── __tests__ # 测试目录
│ │ │ ├── src # 组件入口目录
│ │ │ │ ├── button-custom.ts # 自定义类型,方法
│ │ │ │ └── button-group.ts # button-group组件属性与ts类型
│ │ │ │ └── button-group.vue # button-group组件模板内容
│ │ │ │ └── button.ts #组件属性与 TS 类型
│ │ │ │ └── button.vue #组件模板内容
│ │ │ │ └── constants.ts #常量申明
│ │ │ │ └── instance.ts #button&button-group的组件类型注册
│ │ │ │ └── use-button.ts #组件hook
│ │ │ ├── style # 组件样式目录
│ │ │ └── index.ts # 组件入口文件
│ │ └── package.json
先从基本的button.ts文件开始来看,引入方法、类型
js
import { useSizeProp } from '@element-plus/hooks' //引入字符串枚举类型
import { buildProps, definePropType, iconPropType } from '@element-plus/utils' //props优化工具类,组件类型
import { Loading } from '@element-plus/icons-vue'
import type { Component, ExtractPropTypes } from 'vue' //vue提供的props转换方法类型,component类型
定义需要的类型
js
export const buttonProps = buildProps({
//
***
***
*****
loadingIcon: {
type: iconPropType,
default: () => Loading,
},//loading图标接收组件类型
****
*****
//是否在汉字之间插入空格
autoInsertSpace: {
type: Boolean,
default: undefined,
}, //APi中为注明的属性
/**
* @description custom element tag 自定义的标签可传入组件 v2.3.4 以上版本新增
*/
tag: {
type: definePropType<string | Component>([String, Object]),
default: 'button',
},
})
抛出事件类型,优化之后的props属性
js
export const buttonEmits = {
click: (evt: MouseEvent) => evt instanceof MouseEvent,
}
export type ButtonProps = ExtractPropTypes<typeof buttonProps>
export type ButtonEmits = typeof buttonEmits
export type ButtonType = ButtonProps['type']
export type ButtonNativeType = ButtonProps['nativeType']
export interface ButtonConfigContext {
autoInsertSpace?: boolean
}
button.vue文件
js
//动态组件 默认使用button 2.3.4 以上版本新增 tag组件模式 自定义原型标签
<component :is="tag" ref="_ref" v-bind="_props" :class="buttonKls" :style="buttonStyle" @click="handleClick">
loading插槽
icon插槽
默认内容插槽
自定义内容插槽
****
</component>
const { _ref, _size, _type, _disabled, _props, shouldAddSpace, handleClick } =
useButton(props, emit)
引入自定义事件,
props内容
const ns = useNamespace('button') 使用EBM 定义类
//根据状态设备不同的类名
const buttonKls = computed(() => [
ns.b(),
ns.m(_type.value),
ns.m(_size.value),
ns.is('disabled', _disabled.value),
ns.is('loading', props.loading),
ns.is('plain', props.plain),
ns.is('round', props.round),
ns.is('circle', props.circle),
ns.is('text', props.text),
ns.is('link', props.link),
ns.is('has-bg', props.bg),
])
//使用vue3的defineExpose宏 抛出属性,方法
defineExpose({
/** @description button html element */
ref: _ref,
/** @description button size */
size: _size,
/** @description button type */
type: _type,
/** @description button disabled */
disabled: _disabled,
/** @description whether adding space */
shouldAddSpace,
})
button-group.ts定义button模式下的类型说明
button-group.vue 定义一个个插槽 使用ts定义的属性设置组件接收的prorps,provide 抛出buttonGroup的属性buttonGroupContextKey作为唯一键值
js
provide(
buttonGroupContextKey,
reactive({
size: toRef(props, 'size'),
type: toRef(props, 'type'),
})
)
那么申明 buttonGroupContextKey 用到的 type(InjectionKey
)是什么? InjectionKey
是 Vue 3 组合式 API 中用于依赖注入的键类型,继承自 JavaScript 的 Symbol
类型。它通过类型断言确保 provide
和 inject
函数中使用的键具有明确的类型安全性,主要用于组件间的数据传递; buttonGroupContextKey 的定义在 button/constants.ts的文件中有提到。
instance.ts文件
引入组件并抛出组件实例类型
js
import type Button from './button.vue'
import type ButtonGroup from './button-group.vue'
export type ButtonInstance = InstanceType<typeof Button> & unknown
export type ButtonGroupInstance = InstanceType<typeof ButtonGroup> & unknown
最后需要在 button/index.ts
目录文件中定义 Button 组件的出口内容。
js
// *****
*****代码块
export const ElButton: SFCWithInstall<typeof Button> & {
ButtonGroup: typeof ButtonGroup
} = withInstall(Button, {
ButtonGroup,
})
export const ElButtonGroup: SFCWithInstall<typeof ButtonGroup> =
withNoopInstall(ButtonGroup)
export default ElButton
export * from './src/button'
export * from './src/constants'
export type { ButtonInstance, ButtonGroupInstance } from './src/instance'