refactor: 优化 SafeNFormItem 组件的类型定义和插槽处理
Some checks failed
CI/CD Pipeline / playwright (push) Successful in 2m38s
CI/CD Pipeline / build-and-deploy (push) Failing after 3m13s

This commit is contained in:
严浩
2025-10-31 11:14:13 +08:00
parent 4c7f052ea1
commit 18cb623730
2 changed files with 28 additions and 25 deletions

View File

@@ -63,10 +63,10 @@ function handleValidateClick() {
<SafeNFormItem #default="slotProps" path="user.name"> <SafeNFormItem #default="slotProps" path="user.name">
<div class="border"> <div class="border">
<pre>{{ JSON.stringify({ slotProps }, null, 2) }}</pre> <!-- <pre>{{ JSON.stringify({ slotProps: toRaw(slotProps) }, null, 2) }}</pre> -->
<div>v: {{ slotProps.value }}</div> <div>v: {{ slotProps.value }}</div>
</div> </div>
<NInput :value="slotProps.value" placeholder="SafeNFormItem" /> <NInput v-model:value="slotProps.value" placeholder="SafeNFormItem" />
</SafeNFormItem> </SafeNFormItem>
<n-form-item> <n-form-item>

View File

@@ -1,7 +1,7 @@
// https://www.naiveui.com/zh-CN/os-theme/components/form // https://www.naiveui.com/zh-CN/os-theme/components/form
// initialFormValue // initialFormValue
import { get } from 'lodash-es'; import { get, set } from 'lodash-es';
import type { FormInst } from 'naive-ui'; import type { FormInst } from 'naive-ui';
import type { Get, Paths } from 'type-fest'; import type { Get, Paths } from 'type-fest';
import type { SlotsType } from 'vue'; import type { SlotsType } from 'vue';
@@ -48,45 +48,36 @@ export function useSafeNForm<T extends Record<string, any> = Record<string, unkn
// <<<<< // <<<<<
// >>>>> 创建类型安全的 FormItem 组件 // >>>>> 创建类型安全的 FormItem 组件
type SafeNFormItemProps<P extends Paths<T> & string> = { type SafeNFormItemProps<P extends Paths<T> & string> = {
label?: string; label?: string;
path: P; path: P;
placeholder?: string; placeholder?: string;
}; };
type SafeNFormItemSlotScope<P extends Paths<T> & string> = { type SafeNFormItemDefaultSlot<P extends Paths<T> & string> = {
value: Get<T, P>; value: Get<T, P>;
}; };
type SafeNFormItemSlotsDefinition<P extends Paths<T> & string> = SlotsType<{
default: SafeNFormItemSlotScope<P>;
}>;
type SafeNFormItemSlotFns<P extends Paths<T> & string> = {
default?: (scope: SafeNFormItemSlotScope<P>) => any;
};
type SafeNFormItemComponent = new <P extends Paths<T> & string>(
props: SafeNFormItemProps<P>,
) => {
$props: SafeNFormItemProps<P>;
$slots: SafeNFormItemSlotFns<P>;
};
const SafeNFormItemImpl = defineComponent< const SafeNFormItemImpl = defineComponent<
SafeNFormItemProps<Paths<T> & string>, SafeNFormItemProps<Paths<T> & string>,
/* Emits */ [], /* Emits */ [],
/* EE */ never, /* EE */ never,
SafeNFormItemSlotsDefinition<Paths<T> & string> SlotsType<{ default: SafeNFormItemDefaultSlot<Paths<T> & string> }>
>( >(
(props, ctx) => { (props, ctx) => {
return () => { return () => {
const value = get(formValue.value, props.path) as Get<T, typeof props.path>; // const value = get(formValue.value, props.path) as Get<T, typeof props.path>;
const slots = ctx.slots; const value = computed({
get() {
return get(formValue.value, props.path) as Get<T, typeof props.path>;
},
set(v) {
set(formValue.value, props.path, v);
},
});
return ( return (
<NFormItem path={props.path} label={props.label}> <NFormItem path={props.path} label={props.label}>
{slots.default?.({ value })} {ctx.slots.default?.({ value: value.value })}
</NFormItem> </NFormItem>
); );
}; };
@@ -97,7 +88,19 @@ export function useSafeNForm<T extends Record<string, any> = Record<string, unkn
props: ['label', 'path', 'placeholder'], props: ['label', 'path', 'placeholder'],
}, },
); );
const SafeNFormItem = SafeNFormItemImpl as unknown as SafeNFormItemComponent;
// Expose a generic constructor so template literals narrow `path`.
type SafeNFormItemComponent = {
new <P extends Paths<T> & string>(
props: SafeNFormItemProps<P>,
): {
$props: SafeNFormItemProps<P>;
$slots: {
default?: (scope: SafeNFormItemDefaultSlot<P>) => VNode[];
};
};
};
const SafeNFormItem = SafeNFormItemImpl as SafeNFormItemComponent;
// <<<<< // <<<<<
return { return {