The overall level of Ali's front-end can be said to be at the domestictop
. Related open source component libraries, especiallyant-design
(react
version), have a high usage rate at home and abroad. With the continuous improvement of the front-end technology stack, the corresponding matching component library is also accompanied by the iteration of the version. The iterative process of these component libraries and the implementation of internal components are worth learning for every front-end developer. Especially some very good component libraries.
ant-design-vue component library directory structure
By github
be the component library open source project on clone
or download
after the project directory structure and directory structure of our daily development of the project are different. The structure of several cores is as follows:
antd-tools
: This directory structure contains tools that combine the configuration of popular build tools such aswebpack
,gulp
ant-design-vue
for packaging, building, and publishing.components
: This directory isant-design-vue
library components, and the packages of all public components are under this folderexamples
: A package for testing the packaged component library during the development process (Demo)scripts
: Script collection, used to process some packaged and built configuration scripts of the librarytypings
: A collection of some declaration files written for the library
Component source code analysis
Button
component: button component, which is also one of the most commonly used components in our daily development
Button component source code analysis
The packaging of a component, from a basic point of view. Pay attention to the output and input of the component itself. The input and output here refer to its own attributes and behaviors or the attributes and behaviors passed from the parent.
We can see button
components/button
directory, the structure is as follows:
__tests__
: used to unit test the componentstyle
less
style files that need to be written to encapsulate the component.tsx
: the corresponding component file
button
component in our daily project development process, read the source code from the following questions:
- How are the attributes
type
(such asdefault
,primary
,dashed
etc.) of the component handled internally? - How is
icon
attribute of this component processed internally? loading
attribute of this component processed internally to achieve a similar anti-shake effect?
type
attribute of the Button
component
From ant-design-vue
can see the official website, Button
component has 'default', 'primary', 'ghost', 'dashed', 'link', 'text'
these six types. When we set a different type
, Button
component will also change accordingly.
Open the button.tsx
file, you can see the following code:
import buttonTypes from './buttonTypes';
const props = buttonTypes();
export default defineComponent({
name: 'AButton',
inheritAttrs: false,
__ANT_BUTTON: true,
props,
...
})
- Imported the
buttonTypes
module - Define a
props
variable to receive the return value of the function exported by thebuttonTypes
props
variable to theprops
attribute of theButton
Next, look at the relevant code of the buttonTypes.ts
first line of code : import { tuple } from '../_util/type';
from _tuil
of type
introduced file tuple
function, the function declaration is defined as follows:
export const tuple = <T extends string[]>(...args: T) => args;
uses the ts
in 06173e43cbea5c to constrain that T
can only be a string array (that is, the elements in the array can only be string types), and the return word of the function is a specific string element.
first 7
line : const ButtonTypes = tuple('default', 'primary', 'ghost', 'dashed', 'link', 'text');
, defines a tuple type of a variable ButtonTypes
, the tuple contains six elements.
first 8
line : export type ButtonType = typeof ButtonTypes[number];
, defined here ButtonType
this type and for export;
The 23rd line of code: defines a buttonProps
function, which is the Button
of the props
attributes of the entire 06173e43cbeab5 component. The function code is as follows:
const buttonProps = () => ({
prefixCls: PropTypes.string,
type: PropTypes.oneOf(ButtonTypes),
loading: {
type: [Boolean, Object],
default: (): boolean | { delay?: number } => false,
},
disabled: PropTypes.looseBool,
ghost: PropTypes.looseBool,
block: PropTypes.looseBool,
danger: PropTypes.looseBool,
icon: PropTypes.VNodeChild,
});
It can be seen that type
is vue-types
which can only be one of the 6 mentioned above. From the above analysis, we can conclude that the origin of the type
Button
component, let's see how it is associated with the style of the Button
For example, set type: danger
, then Button
component is red and the text is white. How is this relationship handled internally?
Return to the code Button.tsx
Line 75 defines a calculated attribute classes
. code show as below:
const classes = computed(() => {
const { type, shape, size, ghost, block, danger } = props;
const pre = prefixCls.value;
return {
[`${pre}`]: true,
[`${pre}-${type}`]: type,
[`${pre}-${shape}`]: shape,
[`${pre}-${sizeCls}`]: sizeCls,
[`${pre}-loading`]: innerLoading.value,
[`${pre}-background-ghost`]: ghost && !isUnborderedButtonType(type),
[`${pre}-two-chinese-chars`]: hasTwoCNChar.value && autoInsertSpace.value,
[`${pre}-block`]: block,
[`${pre}-dangerous`]: !!danger,
[`${pre}-rtl`]: direction.value === 'rtl',
};
});
Here you can see type
, and the difference in button style performance is mainly in the two variables pre
and type
pre
represents the prefix of the class
class name of the corresponding component, such as the prefix ant-btn
button
component.
Finally look Button
assembly return
returned a method, as follows:
return () => {
const buttonProps = {
class: [
classes.value,
attrs.class,
],
onClick: handleClick,
};
const buttonNode = (
<button {...buttonProps} ref={buttonNodeRef} type={htmlType}>
{iconNode}
{kids}
</button>
);
if (isUnborderedButtonType(type)) {
return buttonNode;
}
return <Wave ref="wave">{buttonNode}</Wave>;
};
This method is a variable of the object type buttonProps
class
attribute to receive its own class
attribute and the above-mentioned classes
calculation attribute; then a node
buttonNode
, and the node
node in the 06173e43cbebc6 node is the content of the html
of button
. And through jsx
grammar to deconstruct buttonProps
<a-button type="primary">A</a-button>
For example, A
above, we can know that its class name is: ant-btn ant-btn-primary
.
Corresponding styles written in the folder style
You can know that the type
Button
component corresponding to the different 06173e43cbec09 is different.
icon
attributes of the Button
component
When we use the Button
component, for example, the component needs to display a combination of icons and text, we may write like this:
<a-button type="primary">
<template #icon><SearchOutlined /></template>
查询
</a-button>
So how does the component internally handle the code <template #icon><SearchOutlined /></template>
After analyzing the type
attribute of the above-mentioned Button
component, it is found that the definition processing of all attributes of the component is in buttonTypes.ts
, so let's first look at the code related to icon
const buttonProps = () => ({
prefixCls: PropTypes.string,
type: PropTypes.oneOf(ButtonTypes),
icon: PropTypes.VNodeChild,
...
});
You can see, the object returned by the function in a icon
attribute whose type is PropTypes
on the object VNodeChild
type, then this VNodeChild
particular is what is it? Finally, there is a type.ts
file in the vue-types
folder, which defines the type VueNode
export type VueNode = VNodeChild | JSX.Element;
So in the end we can know icon
is Vuejs
a virtual sub-node or JSX
a element.
Next back to the button.tsx
component itself.
export default defineComponent({
name: 'AButton',
slots: ['icon'],
setup(props, { slots, attrs, emit }) {}
}
)
It can be seen that when the assembly is defined by vuejs
its slots
receives attribute icon
this element.
Then look at the code in the method returned by return
const icon = getPropsSlot(slots, props, 'icon');
?? Is it an object returned here or?
This method defines the icon
variable. Next, getPropsSlot
’s look at the function of the 06173e43cbed4d method? In the _util/props-util
file, you can see the implementation of this method, the code is as follows:
function getPropsSlot(slots, props, prop = 'default') {
return props[prop] ?? slots[prop]?.();
}
It can be seen that the effect of this method is used for the processing slot
If props
is a corresponding prop
, return directly, otherwise, get the corresponding prop
slots
to execute the method, and finally return an array. The array contains the corresponding virtual node elements. The attributes of the elements are roughly as follows:
anchor: null
appContext: null
children: "我是icon"
component: null
dirs: null
dynamicChildren: null
dynamicProps: null
el: text
key: null
patchFlag: 0
props: null
ref: null
scopeId: "data-v-c33314ea"
shapeFlag: 8
ssContent: null
ssFallback: null
staticCount: 0
suspense: null
target: null
targetAnchor: null
transition: null
type: Symbol(Text)
__v_isVNode: true
__v_skip: true
The next step is:
const iconNode = innerLoading.value ? <LoadingOutlined /> : icon;
const buttonNode = (
<button {...buttonProps} ref={buttonNodeRef} type={htmlType}>
{iconNode}
{kids}
</button>
);
A icon
node is defined, and the buttonNode
node is directly applied to the button
label in the icon
slot
method (it becomes a child element of the button
Therefore, seen from the following analysis, Button
assembly for icon
treatment is mainly a combination vuejs
of slots
or props
attribute corresponding to meet the conditions prop
virtualization (virtual generate a child node) performed.
loading
attribute of the Button
component
In the process of project development, the Button
component is very high, such as passing some data to the back-end through its click event and storing it in the database after processing. This is a very common business development point, but if we continuously click the button multiple times within 1 or 2 seconds , if no processing is done, a lot of duplicate data will eventually be stored in the database. To solve this problem, we have to use
javascript
to deal with, but the processing of ant-design-vue
in button
encapsulates throttling processing, as long as we use this component, add a loading
attribute. It's okay. How is the encapsulation inside its components achieved?
Still look buttonTypes.ts
this file loading
related code, the code is as follows:
const buttonProps = () => ({
... // other code
loading: {
type: [Boolean, Object],
default: (): boolean | { delay?: number } => false,
},
...// other code
onClick: {
type: Function as PropType<(event: MouseEvent) => void>,
},
});
You can see that in the object returned by the method buttonProps
, there is a property of loading
and a method of onClick
- The value of the
loading
boolean
type or an object type. Its default value isfalse
onClick
method is an event object whose parameter is a mouse event, and the method has no return value
Next back to the button.tsx
component itself.
In this document line 22 a type definition type Loading = boolean | number;
defines Loading
type of numerical or Boolean type
Then a variable is defined in the setup
const innerLoading: Ref<Loading> = ref(false);
innerLoading
variable on the 46th line is a response variable with a value of Boolean or numeric value. So what is the purpose of defining this variable? Continue to look at the code related to it.
In line 52 is defines a loadingOrDelay
calculated attribute. To receive loading
this prop
update
const loadingOrDelay = computed(() =>
typeof props.loading === 'object' && props.loading.delay
? props.loading.delay || true
: !!props.loading,
);
On line 58, monitor the value change of the calculated attribute loadingOrDelay
watch
, and perform related logic processing:
watch(
loadingOrDelay,
val => {
clearTimeout(delayTimeoutRef.value);
if (typeof loadingOrDelay.value === 'number') {
delayTimeoutRef.value = window.setTimeout(() => {
innerLoading.value = val;
}, loadingOrDelay.value);
} else {
innerLoading.value = val;
}
},
{
immediate: true,
},
);
If loadingOrDelay
value is number
type, set a definition, loadingOrDelay
seconds after the loadingOrDelay
assign a value to the latest innerLoading
variables. Otherwise, directly loadingOrDelay
the value of innerLoading
to 06173e43cbefb9.
const delayTimeoutRef = ref(undefined);
Because the timer is set, when the component is about to be destroyed (unloaded), the timer needs to be cleared.
onBeforeUnmount(() => {
delayTimeoutRef.value && clearTimeout(delayTimeoutRef.value);
});
Finally, the click event is logically processed on line 121:
const handleClick = (event: Event) => {
// https://github.com/ant-design/ant-design/issues/30207
if (innerLoading.value || props.disabled) {
event.preventDefault();
return;
}
emit('click', event);
};
It can be seen ant-design-vue
for Button
components loading
realize is actually relatively simple and ingenious. By props
received loading
property, and not directly to a series of process by the attribute value change. Instead, a innerLoading
variable and loadingOrDelay
calculated attributes are internally defined to perform the corresponding logical processing. This is because loading
is passed from an external component and cannot be modified directly.
Button
component reference
Method 1: The Button.tsx
component can be directly imported for use, but it can only be used inside the project. Another by vuejs
to components provided install
processing the component approach.
import type { App, Plugin } from 'vue';
import Button from './button';
/* istanbul ignore next */
Button.install = function (app: App) {
app.component(Button.name, Button);
return app;
};
export default Button as typeof Button & Plugin;
Related knowledge tips
TypeScript
in 06173e43cbf0e4Array combines objects of the same type, while Tuple combines objects of different types
let tom: [string, number] = ['Tom', 25];
Anti-shake and throttling
Throttling: If you are a 7-year-old child, one day your mother is making chocolate cakes. But this cake is not for you but for the guests. At this time, you keep asking her for cakes. In the end she gave you a piece, but you continue to ask her for more cakes. She agrees to give you more cakes, provided that they will give you a piece in an hour. This is because you still continue to ask her for cakes, but she ignores you at this time. Finally an hour later, you get more cakes. If you want to get more cakes, no matter how many times it takes, you will get more cakes in an hour.
对于节流,无论用户触发事件多少次,在给定的时间间隔内,附加的函数都只会执行一次。
Anti-shake: Consider the same cake example. This time you kept asking your mother for cakes. She was very angry and told you that she would give you cakes only if you remained silent for an hour. This means that if you keep asking her, you will not get the cake-you will only get the cake one hour after the last question.
对于防抖,无论用户触发事件多少次,一旦用户停止触发事件,附加函数将仅在指定时间后执行。
Subcomponent
prop
checkAs you can see, in the entire
ant-design-vue
component, the verification of the sub-componentprops
is processed by thevue-types
plug-in. The first point is to reduce the amount of code, and the second point is to facilitate reading and expansion.vue-types
plug-in provides thecreateTypes
method, we can use this method to expand more types, for example, the method inant-design-vue
is as follows:import { createTypes } from 'vue-types'; const PropTypes = createTypes({ func: undefined, bool: undefined, string: undefined, number: undefined, array: undefined, object: undefined, integer: undefined, }); PropTypes.extend([ { name: 'looseBool', getter: true, type: Boolean, default: undefined, }, { name: 'style', getter: true, type: [String, Object], default: undefined, }, { name: 'VNodeChild', getter: true, type: null, }, ]);
Component packaging rules
- Components are used: should start from the feelings of the user (programmer)
- There is no "how to do it": the characteristics of the project need to be considered
- Good components are not designed, but modified: often adjusted, sometimes refactored
- The function of the component should be single and simple: don't try to squeeze many functions into one component. Embody the single responsibility principle
- ...
Encapsulated components for others to use
For encapsulated public components, when encapsulating components, you must consider how to let others introduce and use them. At present, a more popular
npm
to manage the component library through 06173e43cbf3a4, and then users can introduce and use corresponding components on demand. This requires the "installation" and "export" operations of a certain component when the component is packaged./* istanbul ignore next */ Button.install = function (app: App) { app.component(Button.name, Button); return app; }; export default Button
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。