自定义原件
如果内置的原件无法满足你的需求,我们也支持自定义原件。
原件协议
ts
export interface Atomic<T = any, P extends AtomicCommonProps<T> = AtomicCommonProps<T>> {
/**
* 名称,命名规范遵循 kebab-case, 需要避免和其他组件冲突
*/
name: string;
/**
* 描述
*/
description?: string;
/**
* 原件作者
*/
author?: string;
/**
* 是否为 只预览模式。可以提示组件实现者,在编辑模式下隐藏该原件
*/
previewOnly?: boolean;
/**
* 是否为 只编辑模式。可以提示组件实现者,在预览模式下隐藏该原件
*/
editOnly?: boolean;
/**
* 组件实现, 就是一个渲染函数
*/
component: (props: P) => any;
/**
* 值验证
* @param {T} value 当前值
* @param {any} context 上下文,可以获取到其他字段的值
* 验证失败抛出异常
*/
validate?: (value: any, props: P, context: any) => Promise<void>;
/**
* 验证触发的时机
*/
validateTrigger?: 'change' | 'blur';
}
命名规范
- 使用 kebab-case, 例如 date-range
- 使用名词/动名词。例如 file 取代 upload, files 取代 upload-multiple
组件实现
component
是原件的实现,这是这是一个渲染函数,接收 props
并返回 VNodeChild
。 以下 props 是固定的:
tsx
export interface AtomicCommonProps<T> {
/**
* 渲染模式
*/
mode?: 'editable' | 'preview';
/**
* 场景值
* 场景值给原件提供了额外的信息,从而可以提供更合理的默认行为
*/
scene?: 'table' | 'form';
/**
* 是否已禁用
*/
disabled?: boolean;
/**
* 字段值
*/
value?: T;
/**
* 字段变化
*/
onChange?: (value?: T) => void;
/**
* 类名
*/
class?: ClassValue;
/**
* 样式
*/
style?: StyleValue;
/**
* 上下文信息,由具体的应用组件指定
*
* note: 通用原件通常不会直接耦合具体的上下文信息
*/
context?: any;
}
由上可见,原件最核心的是 mode
以及 value/onChange
协议。
mode
决定是以预览模式
呈现还是编辑模式
value/onChange
是典型的表单协议。 类似于 Vue 的v-model
原件实现示例
以最简单的 switch
原件为例:
tsx
import { ElSwitchProps, ElSwitch } from 'element-plus';
import { defineAtomic, defineAtomicComponent, DefineAtomicProps, globalRegistry } from '@wakeadmin/components';
/**
* 定义原件 props
*/
export type ASwitchProps = DefineAtomicProps<
// 🔴 原件的值类型,switch 在原件支持 value 为 string | number | boolean 类型
string | number | boolean,
// 🔴 继承的属性
ElSwitchProps,
// 🔴 拓展的的属性
{
renderPreview?: (active: boolean) => any;
/**
* 默认同 activeText
*/
previewActiveText?: string;
/**
* 预览时 inactiveText
*/
previewInactiveText?: string;
}
>;
// 🔴 注册到全局的 AtomicProps 中
// 这个让 FatTable、FatForm 在使用时可以从 valueType 推断出 valueProps 的类型
declare global {
interface AtomicProps {
switch: ASwitchProps;
}
}
// 🔴 组件实现, 类似于 defineComponent
export const ASwitchComponent = defineAtomicComponent(
(props: ASwitchProps) => {
// 这里可以放置 Composition API
// 返回 JSX.Element
return () => {
const { value, mode, onChange, context, scene, renderPreview, ...other } = props;
const activeValue = other.activeValue ?? true;
const active = value === activeValue;
const activeText = other.previewActiveText ?? other.activeText ?? '开启';
const inactiveText = other.previewInactiveText ?? other.inactiveText ?? '关闭';
// 预览模式
if (mode === 'preview') {
return renderPreview ? renderPreview(active) : <span>{active ? activeText : inactiveText}</span>;
}
// 编辑模式
return <ElSwitch {...other} modelValue={value} onUpdate:modelValue={onChange} />;
};
},
{ name: 'ASwitch' }
);
// 🔴 注册
globalRegistry.register('switch', {
name: 'switch',
component: ASwitchComponent,
description: '开关',
author: 'ivan-lee',
});
如果想深入了解自定义原件的开发细节,可以参考内置原件的实现。
使用自定义原件
使用自定义原件
两种方式:
如果使用
globalRegistry.register
注册的原件。可以通过字符串名称使用, 例如:vue<FatFormItem value-type="switch" />
直接将 atomic 定义传入, 例如
tsxconst switch = defineAtomic({ name: 'switch', component: ASwitchComponent, description: '开关', author: 'ivan-lee', }) // 使用 <FatFormItem valueType={switch} />