文章目录
- 准备工作
- 属性
- 方法
- 模板
- 使用到的hooks
- use-prop
- useDeprecated
看源码时候做的笔记。
准备工作
本地开发 | Element Plus (element-plus.org)
文档与源码对应:docs/examples/button
组件源码位置: packages/components/button/src
属性
定义在 packages/components/button/src/button.ts
(下面路径packages/components/button
省略。)
以下面三个属性为例。buttonProps
定义了button有哪些属性,和属性的类型。如size
的类型是useSizeProp
,即是字符串类型的,values值是枚举值,非必填的属性。
export const buttonProps = buildProps({
/**
* @description button size
*/
size: useSizeProp,
/**
* @description disable the button
*/
disabled: Boolean,
/**
* @description button type
*/
type: {
type: String,
values: buttonTypes,
default: '',
})
useSizeProp
:
export const useSizeProp = buildProp({
type: String,
values: componentSizes,
required: false,
} as const)
componentSizes
:
export const componentSizes = ['', 'default', 'small', 'large'] as const
buildProps
是一个构建属性的方法,没看懂。
buttonProps
-> prop
:从buttonProps
到button.vue 的props
// buttonProps -> ButtonProps
// src / button.ts
export type ButtonProps = ExtractPropTypes<typeof buttonProps>
// ButtonProps -> useButton
// src / use-button.ts
export const useButton = (
props: ButtonProps,
emit: SetupContext<ButtonEmits>['emit']
) => {...}
// buttonProps-> props
// src / button.vue
const props = defineProps(buttonProps)
// src / button.vue
// 用props创建button实例,并解构出一堆属性
const { _ref, _size, _type, _disabled, _props, shouldAddSpace, handleClick } =
useButton(props, emit)
调用useButton创建button实例,解构出一堆属性。
这些属性一部分绑定到class上,如:_size, _type, _disabled
和其他props的属性
。用于控制按钮的样式。
// useNamespace,用于生成 BEM(Block Element Modifier)命名规则的类名和 CSS 变量名
const ns = useNamespace('button')
// 生成符合 BEM 命名规则的类名,绑定到元素的class上
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),
... // 其他props的属性
])
// 绑定在class上
<component
:is="tag"
ref="_ref"
v-bind="_props"
:class="buttonKls" // 这里
:style="buttonStyle"
@click="handleClick"
>
一部分绑定到属性上:v-bind="_props"
解构出的点击事件handleClick
绑定到@click
上。
解构出的shouldAddSpace
通过defineExpose
暴露出去:
// button.vue
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方法:
可以通过this.refs.refName
调用看到:
// template
<el-button ref="buttonRef">Button</el-button>
// script
console.log(this.$refs.buttonRef);
方法
buttonEmits
-> button实例。
// src / button.ts
export const buttonEmits = {
click: (evt: MouseEvent) => evt instanceof MouseEvent,
}
// src / button.vue
const emit = defineEmits(buttonEmits)
const { _ref, _size, _type, _disabled, _props, shouldAddSpace, handleClick } =
useButton(props, emit)
解构出的handleClick 是方法,绑定在@click上:@click="handleClick"
模板
- tag默认为button(在
button.ts
中定义的buttonProps中) - _ref为
ref<HTMLButtonElement>()
,在use-button.ts
中定义的useButton中 - _props为useButton创建的实例解构出的属性,动态绑定到模板上
- buttonKls为生成的符合 BEM 命名规则的类名,绑定到class上
- buttonStyle为根据props生成的样式,绑定在style上
- handleClick为解构出来的点击事件
template、el-icon、span 对应插槽。
<template>
<component
:is="tag"
ref="_ref"
v-bind="_props"
:class="buttonKls"
:style="buttonStyle"
@click="handleClick"
>
<!-- 插槽-loading -->
<template v-if="loading">
<slot v-if="$slots.loading" name="loading" />
<el-icon v-else :class="ns.is('loading')">
<component :is="loadingIcon" />
</el-icon>
</template>
<!-- 插槽-icon -->
<el-icon v-else-if="icon || $slots.icon">
<component :is="icon" v-if="icon" />
<slot v-else name="icon" />
</el-icon>
<!-- 插槽-default -->
<span
v-if="$slots.default"
:class="{ [ns.em('text', 'expand')]: shouldAddSpace }"
>
<slot />
</span>
</component>
</template>
使用到的hooks
路径:packages/hooks/
下
use-prop
路径:/use-prop
作用:传入一个name,返回实例的name属性。
获取当前Vue实例,返回一个计算属性,其值是:实例的某个属性。
vm.proxy 是当前 Vue 实例的代理对象,$props 是代理对象的属性集合,[name] 是指定的属性。
import { computed, getCurrentInstance } from 'vue'
import type { ComputedRef } from 'vue'
export const useProp = <T>(name: string): ComputedRef<T | undefined> => {
// 获取当前Vue实例
const vm = getCurrentInstance()
// 返回当前实例的某个属性
return computed(() => (vm?.proxy?.$props as any)?.[name])
}
useDeprecated
路径:use-deprecated
作用:显示已弃用的警告
// 显示已弃用的警告
export const useDeprecated = (
{ from, replacement, scope, version, ref, type = 'API' }: DeprecationParam,
condition: MaybeRef<boolean>
) => {
watch(
() => unref(condition), // 如果condition是ref,就返回这个ref的内部值;否则返回它本身
(val) => {
if (val) {
debugWarn(
scope,
`[${type}] ${from} is about to be deprecated in version ${version}, please use ${replacement} instead.
For more detail, please visit: ${ref}
`
)
}
},
{
immediate: true,
// 无论 condition 的初始值是什么,回调函数都会在创建侦听器时立即执行一次
}
)
}
如:在button中,当传入的type为text时,props.type === 'text'
返回true
,就会触发useDeprecated
中的watch()
,告诉用户text要被弃用,建议用link替代。
useDeprecated(
{
from: 'type.text',
replacement: 'link',
version: '3.0.0',
scope: 'props',
ref: 'https://element-plus.org/en-US/component/button.html#button-attributes',
},
computed(() => props.type === 'text')
)