feat: add form item rest

v2.3
tangjinzhou 3 years ago
parent 648ab4c4b9
commit 8e111e6a7c

@ -74,7 +74,7 @@ export type { EmptyProps } from './empty';
export { default as Empty } from './empty';
export type { FormProps, FormItemProps } from './form';
export { default as Form, FormItem } from './form';
export { default as Form, FormItem, FormItemRest } from './form';
export { default as Grid } from './grid';

@ -272,20 +272,25 @@ export default defineComponent({
resetField,
});
useProvideFormItemContext({
id: fieldId,
onFieldBlur: () => {
if (props.autoLink) {
onFieldBlur();
}
},
onFieldChange: () => {
if (props.autoLink) {
onFieldChange();
}
useProvideFormItemContext(
{
id: fieldId,
onFieldBlur: () => {
if (props.autoLink) {
onFieldBlur();
}
},
onFieldChange: () => {
if (props.autoLink) {
onFieldChange();
}
},
clearValidate,
},
clearValidate,
});
computed(() => {
return !!(props.autoLink && formContext.model.value && fieldName.value);
}),
);
let registered = false;
watch(
fieldName,

@ -1,5 +1,15 @@
import type { ComputedRef, InjectionKey } from 'vue';
import { computed, inject, provide } from 'vue';
import type { ComputedRef, InjectionKey, ConcreteComponent } from 'vue';
import {
watch,
computed,
inject,
provide,
ref,
onBeforeUnmount,
getCurrentInstance,
defineComponent,
} from 'vue';
import devWarning from '../vc-util/devWarning';
export type FormItemContext = {
id: ComputedRef<string>;
@ -8,24 +18,88 @@ export type FormItemContext = {
clearValidate: () => void;
};
type ContextProps = FormItemContext;
type InternalFormItemContext = {
addFormItemField: (key: Symbol, type: ConcreteComponent) => void;
removeFormItemField: (key: Symbol) => void;
};
const ContextKey: InjectionKey<FormItemContext> = Symbol('ContextProps');
const ContextKey: InjectionKey<ContextProps> = Symbol('ContextProps');
const InternalContextKey: InjectionKey<InternalFormItemContext> = Symbol('InternalContextProps');
export const useProvideFormItemContext = (props: ContextProps) => {
export const useProvideFormItemContext = (
props: FormItemContext,
useValidation: ComputedRef<boolean> = computed(() => true),
) => {
const formItemFields = ref(new Map<Symbol, ConcreteComponent>());
const addFormItemField = (key: Symbol, type: ConcreteComponent) => {
formItemFields.value.set(key, type);
formItemFields.value = new Map(formItemFields.value);
};
const removeFormItemField = (key: Symbol) => {
formItemFields.value.delete(key);
formItemFields.value = new Map(formItemFields.value);
};
const instance = getCurrentInstance();
watch([useValidation, formItemFields], () => {
if (process.env.NODE_ENV !== 'production') {
if (useValidation.value && formItemFields.value.size > 1) {
devWarning(
false,
'Form.Item',
`FormItem can only collect one field item, you haved set ${[
...formItemFields.value.values(),
]
.map(v => `\`${v.name}\``)
.join(', ')} ${formItemFields.value.size} field items.
You can set not need to be collected fields into \`a-form-item-rest\``,
);
let cur = instance;
while (cur.parent) {
console.warn('at', cur.type);
cur = cur.parent;
}
}
}
});
provide(ContextKey, props);
provide(InternalContextKey, {
addFormItemField,
removeFormItemField,
});
};
const defaultContext: FormItemContext = {
id: computed(() => undefined),
onFieldBlur: () => {},
onFieldChange: () => {},
clearValidate: () => {},
};
const defaultInternalContext: InternalFormItemContext = {
addFormItemField: () => {},
removeFormItemField: () => {},
};
export const useInjectFormItemContext = () => {
const defaultContext: ContextProps = {
id: computed(() => undefined),
onFieldBlur: () => {},
onFieldChange: () => {},
clearValidate: () => {},
};
const internalContext = inject(InternalContextKey, defaultInternalContext);
const formItemFieldKey = Symbol('FormItemFieldKey');
const instance = getCurrentInstance();
internalContext.addFormItemField(formItemFieldKey, instance.type);
onBeforeUnmount(() => {
internalContext.removeFormItemField(formItemFieldKey);
});
// We should prevent the passing of context for children
provide(InternalContextKey, defaultInternalContext);
provide(ContextKey, defaultContext);
return inject(ContextKey, defaultContext);
};
export default defineComponent({
name: 'AFormItemRest',
setup(_, { slots }) {
provide(InternalContextKey, defaultInternalContext);
provide(ContextKey, defaultContext);
return () => {
return slots.default?.();
};
},
});

@ -116,12 +116,14 @@ The first is to use multiple `a-form-item`:
</a-form-item>
```
The second way is to wrap it with a custom component and call `useFormItemContext` in the custom component
The second way is to wrap it with a custom component and call `useFormItemContext` in the custom component, It is equivalent to merging multiple form items into one.
```html
<script>
// custom component
import { Form } from 'ant-desing-vue';
export default {
name: 'custom-name',
setup() {
const formItemContext = Form.useFormItemContext();
},
@ -138,6 +140,15 @@ The second way is to wrap it with a custom component and call `useFormItemContex
</a-form-item>
```
Third, the component library provides an `a-form-item-rest` component, which will prevent data collection. You can put form items that do not need to be collected and verified into this component. It is the same as the first This method is very similar, but it does not generate additional dom nodes.
```html
<a-form-item>
<a-input name="a"></a-input>
<a-form-item-rest><a-input name="b"></a-input></a-form-item-rest>
</a-form-item>
```
#### 2.x
Form.Item hijacks the only child element and listens to the `blur` and `change` events to achieve the purpose of automatic verification, so please make sure that the form field is not wrapped by other elements. If there are multiple child elements, only the change of the first child element will be monitored.

@ -2,25 +2,28 @@ import type { App, Plugin } from 'vue';
import Form, { formProps } from './Form';
import FormItem, { formItemProps } from './FormItem';
import useForm from './useForm';
import { useInjectFormItemContext } from './FormItemContext';
import FormItemRest, { useInjectFormItemContext } from './FormItemContext';
export type { Rule, RuleObject } from './interface';
export type { FormProps } from './Form';
export type { FormItemProps } from './FormItem';
Form.useInjectFormItemContext = useInjectFormItemContext;
Form.ItemRest = FormItemRest;
/* istanbul ignore next */
Form.install = function (app: App) {
app.component(Form.name, Form);
app.component(Form.Item.name, Form.Item);
app.component(FormItemRest.name, FormItemRest);
return app;
};
export { FormItem, formItemProps, formProps, useForm, useInjectFormItemContext };
export { FormItem, formItemProps, formProps, FormItemRest, useForm, useInjectFormItemContext };
export default Form as typeof Form &
Plugin & {
readonly Item: typeof Form.Item;
readonly ItemRest: typeof FormItemRest;
readonly useForm: typeof useForm;
readonly useInjectFormItemContext: typeof useInjectFormItemContext;
};

@ -104,7 +104,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/ORmcdeaoO/Form.svg
</a-form-item>
```
如上 Form.Item 并不知道需要收集 `name="a"` 还是 `name=`b``,你可以通过如下种方式去解决此类问题:
如上 Form.Item 并不知道需要收集 `name="a"` 还是 `name=`b``,你可以通过如下种方式去解决此类问题:
第一种,使用多个 `a-form-item`:
@ -115,10 +115,11 @@ cover: https://gw.alipayobjects.com/zos/alicdn/ORmcdeaoO/Form.svg
</a-form-item>
```
第二种,使用自定义组件包裹,并在自定义组件中调用 `useFormItemContext`
第二种,使用自定义组件包裹,并在自定义组件中调用 `useFormItemContext`,相当于把多个表单项合并成了一个
```html
<script>
// 自定义组件
import { Form } from 'ant-desing-vue';
export default {
setup() {
@ -137,6 +138,15 @@ cover: https://gw.alipayobjects.com/zos/alicdn/ORmcdeaoO/Form.svg
</a-form-item>
```
第三种,组件库提供了一个 `a-form-item-rest` 组件,它会阻止数据的收集,你可以将不需要收集校验的表单项放到这个组件中即可,它和第一种方式很类似,但它不会产生额外的 dom 节点。
```html
<a-form-item>
<a-input name="a"></a-input>
<a-form-item-rest><a-input name="b"></a-input></a-form-item-rest>
</a-form-item>
```
#### 2.x
Form.Item 会对唯一子元素进行劫持,并监听 `blur``change` 事件,来达到自动校验的目的,所以请确保表单域没有其它元素包裹。如果有多个子元素,将只会监听第一个子元素的变化。

@ -49,6 +49,7 @@ Array [
"Empty",
"Form",
"FormItem",
"FormItemRest",
"Grid",
"Input",
"InputGroup",

Loading…
Cancel
Save