组件开发
APlus-UI通用组件和业务组件编写指南
目录结构
组件开发写在目录aplus-ui/src下,比如Button组件目录结构如下:
-- aplus-ui
-- src
-- button
__test__ 测试用例目录
-- style 样式目录
-- index.ts 样式文件
-- button.vue 组件
-- index.ts 组件导出入口
-- index.ts 所有组件导出入口文件
-- locale 组件国际化配置目录
-- utils 组件通用的utils目录
-- type.ts 组件库通用类型定义
-- compoennt.d.ts 全局组件类型导出文件
-- index.ts 组件库入口文件对于业务组件而言,则需要写在aplus-ui/src/business目录下。
如何确定业务组件?
业务组件一般是通用组件(如antdv中的组件)的上层封装,更低的抽象、包含特定的业务逻辑和交互、特定的适用场景。
如果组件有通用的适用场景(如对通用组件做功能增强),在APlus-UI中仍然被认为通用组件。
开发流程
以下流程假定你在开发组件的同时编写文档
在根目录运行pnpm docs:dev启动文档的开发服务,该服务会在组件/样式/文档变动后自动重启(或热更新)
为组件编写样式
编写样式
所有的组件的样式都应该编写在src/[component-name]/style目录下。
一个完整的样式文件包含以下几个部分:
- 确定需要将那些属性设置为
Design Token,并且创建对应组件的token类型,比如:
export type ActionToken = {
/**
* 加载中时的组件透明度
*/
loadingOpacity: number;
};上例为ApAction组件创建了token类型,其中包含一个属性loadingOpacity即自定义加载中时的组件透明度。
- 编写具体的样式,创建一个类型为
GenStyleFunc的函数(一般命名为gen[componentName]Style),然后编写样式即可,比如:
const genActionStyle: GenStyleFunc<ActionToken> = (token) => {
const { componentCls } = token;
return {
[componentCls]: {
width: '33.3333%',
border: '1px solid gray',
'&--loading': {
opacity: token.loadingOpacity
}
}
}
}- 使用
genComponentStyleHook函数组装编写的样式,并返回一个可组合函数(Hook)。
export default genComponentStyleHook<ActionToken>(
'ApAction',
(token) => {
return [genActionStyle(token)];
},
{ loadingOpacity: 0.35 },
{
skipUnit: ['loadingOpacity']
}
);上例传递了token的默认值,并通过option.skipUnit跳过为token添加单位(px)。
在组件中使用样式
在组件中,需要使用useNamespace提供的api来获取类名,例如:
const { b, m } = useNamespace('action-item');导入样式文件中的可组合函数,
import useStyle from './style';然后使用这些useStyle和这些api来生成最终的类名,例如:
<template>
<div :class="[b(), m(color), hashId]" />
</template>
<script setup lang="ts">
const { b, m } = useNamespace('action-item');
const hashId = useStyle('action-item');
</script>useStyle会返回一个hash后的类名,需要将其绑定节点上,如果遇到组件中包含全局组件(Modal等),可能需要将hashId多次绑定。
TIP
以上在编写CSS时,请遵循BEM命名规范。
主题和 UI Mode
Aplus-UI基于@aplus-frontend/antdv开发,antdv通过Design Token实现主题,而APlus-UI使用Design Token以及UI模式来确定主题。
你可以在src/design-token目录下通过design-token来修改组件的token,同时此目录下也托管了antdv在不同UI模式下的token预设以及APlus-UI自身在uiMode='admin'下的token预设。
使用全局样式覆盖?
如果你想通过全局样式覆盖的方式修改antdv组件,请不要这么做,其可能会造成难以估量的后果!
更好的方式是,使用design-token或者开发一个新组件,在该组件下通过样式覆盖的方式修改 css 被认为是允许的。
如果新开发的组件则建议使用以下两种方式来兼容 UI 风格:
- 如果不同的UI风格会导致渲染结构或是样式属性上的区别,建议使用
uiMode加以判断。
<template>
<div :class="[b(), m(uiMode)]" />
</template>
<script setup lang="ts">
const { b, m } = useNamespace('action-item');
const uiMode = useGlobalConfig('uiMode'); // aplus | admin
</script>在编写css时,建议分为通用类、UI 风格类。
- 如果 UI 风格并不会导致结构上的差异,则可以使用Design Token,默认情况下组件支持
uiMode='aplus'的UI模式,然后将另一个UI模式的token维护到design-token目录中对应的预设下。
多语言
如果组件有多语言的需求,请将多语言词条在locale/lang对应语言文件中,首先查看common下是否有合适的多语言词条,如果没有则需要按照规则新建词条即可,请注意,为了让多语言能够正常工作,你需要在语言文件中保持一致的结构。
维护好词条后,需要在组件代码中使用useLocale来获取多语言和翻译函数。
<template>
<p>{{ t('ap.common.more') }}</p>
</template>
<script setup lang="ts">
import { useLocale } from '../../config-provider';
const { t } = useLocale();
</script>全局化配置
在开发组件时,你还可以通过ConfigProvider组件设置全局生效的配置,这对于某些业务组件而言可能会很有用,使用全局化配置很简单,主要有两步:
- 定义类型
在config-provider目录下的config-provider-props.ts定义配置的类型
提示
如果是配置全局接口,请在ApiType下添加即可。
- 添加到全局配置中
在config-provider/config-provider.tsx文件中,修改以下代码:
const cfg = computed(() => ({
...,
myConfig: props.myConfig
// 添加你的全局化配置
}));设置完后,就可以通过useGlobalConfig('myConfig')在组件中,拿到全局化配置了。
全局注册
如果有全局注册的需求(例如Button),请使用如下代码:
import _Button from './button.vue';
import { withInstall } from '@aplus-frontend/utils';
export const Button = withInstall(_Button);
export default Button;并在components.d.ts中添加该组件的类型声明。
编写测试用例
本项目内置了vitest和@vue/test-utils,因此你可以很方便为组件编写单元测试。
- 在组件目录下新建
__test__目录用于存放测试用例。 - 新建
xx.test.ts文件并编写测试用例,例如:
describe('ApFieldSlider', () => {
test('should read mode works', async () => {
const wrapper = mount(ApFieldSlider, {
props: {
value: 10,
mode: 'read'
}
});
expect(wrapper.text()).toBe('10');
await wrapper.setProps({
range: true,
value: [10, 90]
});
expect(wrapper.text()).toBe('10-90');
});
});- 运行
pnpm test运行测试用例,你也可以安装vitest的VSCode插件使用图形化界面来运行用例。