mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-28 13:09:40 +08:00
docs: More info about useWatch (#35039)
* docs: more about useWatch * test: Update test case * docs: more info * docs: more & more * feat: add useFormInstance * chore: add version info
This commit is contained in:
parent
9b73058045
commit
beeaf2d221
@ -101,8 +101,9 @@ const InternalForm: React.ForwardRefRenderFunction<FormInstance, FormProps> = (p
|
||||
colon: mergedColon,
|
||||
requiredMark: mergedRequiredMark,
|
||||
itemRef: __INTERNAL__.itemRef,
|
||||
form: wrapForm,
|
||||
}),
|
||||
[name, labelAlign, labelCol, wrapperCol, layout, mergedColon, mergedRequiredMark],
|
||||
[name, labelAlign, labelCol, wrapperCol, layout, mergedColon, mergedRequiredMark, wrapForm],
|
||||
);
|
||||
|
||||
React.useImperativeHandle(ref, () => wrapForm);
|
||||
|
@ -14864,10 +14864,10 @@ Array [
|
||||
>
|
||||
<label
|
||||
class=""
|
||||
for="field1"
|
||||
title="Field 1 (Watch to trigger rerender)"
|
||||
for="name"
|
||||
title="Name (Watch to trigger rerender)"
|
||||
>
|
||||
Field 1 (Watch to trigger rerender)
|
||||
Name (Watch to trigger rerender)
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
@ -14881,7 +14881,7 @@ Array [
|
||||
>
|
||||
<input
|
||||
class="ant-input"
|
||||
id="field1"
|
||||
id="name"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
@ -14897,10 +14897,10 @@ Array [
|
||||
>
|
||||
<label
|
||||
class=""
|
||||
for="field2"
|
||||
title="Field 2 (Not Watch)"
|
||||
for="age"
|
||||
title="Age (Not Watch)"
|
||||
>
|
||||
Field 2 (Not Watch)
|
||||
Age (Not Watch)
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
@ -14912,12 +14912,80 @@ Array [
|
||||
<div
|
||||
class="ant-form-item-control-input-content"
|
||||
>
|
||||
<input
|
||||
class="ant-input"
|
||||
id="field2"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
class="ant-input-number ant-input-number-in-form-item"
|
||||
>
|
||||
<div
|
||||
class="ant-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-up"
|
||||
role="button"
|
||||
unselectable="on"
|
||||
>
|
||||
<span
|
||||
aria-label="up"
|
||||
class="anticon anticon-up ant-input-number-handler-up-inner"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="up"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M890.5 755.3L537.9 269.2c-12.8-17.6-39-17.6-51.7 0L133.5 755.3A8 8 0 00140 768h75c5.1 0 9.9-2.5 12.9-6.6L512 369.8l284.1 391.6c3 4.1 7.8 6.6 12.9 6.6h75c6.5 0 10.3-7.4 6.5-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-down"
|
||||
role="button"
|
||||
unselectable="on"
|
||||
>
|
||||
<span
|
||||
aria-label="down"
|
||||
class="anticon anticon-down ant-input-number-handler-down-inner"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="down"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-input-number-input-wrap"
|
||||
>
|
||||
<input
|
||||
autocomplete="off"
|
||||
class="ant-input-number-input"
|
||||
id="age"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -14927,7 +14995,7 @@ Array [
|
||||
class="ant-typography"
|
||||
>
|
||||
<pre>
|
||||
Field 1 Value:
|
||||
Name Value:
|
||||
</pre>
|
||||
</article>,
|
||||
]
|
||||
|
@ -6112,10 +6112,10 @@ Array [
|
||||
>
|
||||
<label
|
||||
class=""
|
||||
for="field1"
|
||||
title="Field 1 (Watch to trigger rerender)"
|
||||
for="name"
|
||||
title="Name (Watch to trigger rerender)"
|
||||
>
|
||||
Field 1 (Watch to trigger rerender)
|
||||
Name (Watch to trigger rerender)
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
@ -6129,7 +6129,7 @@ Array [
|
||||
>
|
||||
<input
|
||||
class="ant-input"
|
||||
id="field1"
|
||||
id="name"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
@ -6145,10 +6145,10 @@ Array [
|
||||
>
|
||||
<label
|
||||
class=""
|
||||
for="field2"
|
||||
title="Field 2 (Not Watch)"
|
||||
for="age"
|
||||
title="Age (Not Watch)"
|
||||
>
|
||||
Field 2 (Not Watch)
|
||||
Age (Not Watch)
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
@ -6160,12 +6160,80 @@ Array [
|
||||
<div
|
||||
class="ant-form-item-control-input-content"
|
||||
>
|
||||
<input
|
||||
class="ant-input"
|
||||
id="field2"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
class="ant-input-number ant-input-number-in-form-item"
|
||||
>
|
||||
<div
|
||||
class="ant-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-up"
|
||||
role="button"
|
||||
unselectable="on"
|
||||
>
|
||||
<span
|
||||
aria-label="up"
|
||||
class="anticon anticon-up ant-input-number-handler-up-inner"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="up"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M890.5 755.3L537.9 269.2c-12.8-17.6-39-17.6-51.7 0L133.5 755.3A8 8 0 00140 768h75c5.1 0 9.9-2.5 12.9-6.6L512 369.8l284.1 391.6c3 4.1 7.8 6.6 12.9 6.6h75c6.5 0 10.3-7.4 6.5-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-down"
|
||||
role="button"
|
||||
unselectable="on"
|
||||
>
|
||||
<span
|
||||
aria-label="down"
|
||||
class="anticon anticon-down ant-input-number-handler-down-inner"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="down"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-input-number-input-wrap"
|
||||
>
|
||||
<input
|
||||
autocomplete="off"
|
||||
class="ant-input-number-input"
|
||||
id="age"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -6175,7 +6243,7 @@ Array [
|
||||
class="ant-typography"
|
||||
>
|
||||
<pre>
|
||||
Field 1 Value:
|
||||
Name Value:
|
||||
</pre>
|
||||
</article>,
|
||||
]
|
||||
|
@ -1080,4 +1080,30 @@ describe('Form', () => {
|
||||
expect(wrapper.find('.ant-form-item-no-colon')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('useFormInstance', () => {
|
||||
let formInstance;
|
||||
let subFormInstance;
|
||||
|
||||
const Sub = () => {
|
||||
const formSub = Form.useFormInstance();
|
||||
subFormInstance = formSub;
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const Demo = () => {
|
||||
const [form] = Form.useForm();
|
||||
formInstance = form;
|
||||
|
||||
return (
|
||||
<Form form={form}>
|
||||
<Sub />
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
render(<Demo />);
|
||||
expect(subFormInstance).toBe(formInstance);
|
||||
});
|
||||
});
|
||||
|
@ -6,7 +6,7 @@ import { FormProviderProps as RcFormProviderProps } from 'rc-field-form/lib/Form
|
||||
import { FC, PropsWithChildren, ReactNode, useMemo } from 'react';
|
||||
import { ColProps } from '../grid/col';
|
||||
import { FormLabelAlign } from './interface';
|
||||
import { RequiredMark } from './Form';
|
||||
import { FormInstance, RequiredMark } from './Form';
|
||||
import { ValidateStatus } from './FormItem';
|
||||
|
||||
/** Form Context. Set top form style and pass to Form Item usage. */
|
||||
@ -20,6 +20,7 @@ export interface FormContextProps {
|
||||
wrapperCol?: ColProps;
|
||||
requiredMark?: RequiredMark;
|
||||
itemRef: (name: (string | number)[]) => (node: React.ReactElement) => void;
|
||||
form?: FormInstance;
|
||||
}
|
||||
|
||||
export const FormContext = React.createContext<FormContextProps>({
|
||||
|
@ -8,33 +8,33 @@ title:
|
||||
|
||||
## zh-CN
|
||||
|
||||
`useWatch` 允许你监听字段变化,同时仅当改字段变化时重新渲染。
|
||||
`useWatch` 允许你监听字段变化,同时仅当改字段变化时重新渲染。API 文档请[查阅此处](#Form.useWatch)。
|
||||
|
||||
## en-US
|
||||
|
||||
`useWatch` helps watch the field change and only re-render for the value change.
|
||||
`useWatch` helps watch the field change and only re-render for the value change. [API Ref](#Form.useWatch).
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { Form, Input, Typography } from 'antd';
|
||||
import { Form, Input, InputNumber, Typography } from 'antd';
|
||||
|
||||
const Demo = () => {
|
||||
const [form] = Form.useForm<{ field1: string; field2: string }>();
|
||||
const nameValue = Form.useWatch(['field1'], form);
|
||||
const [form] = Form.useForm<{ user: { name: string; age: number } }>();
|
||||
const nameValue = Form.useWatch('name', form);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form form={form} layout="vertical" autoComplete="off">
|
||||
<Form.Item name="field1" label="Field 1 (Watch to trigger rerender)">
|
||||
<Form.Item name="name" label="Name (Watch to trigger rerender)">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="field2" label="Field 2 (Not Watch)">
|
||||
<Input />
|
||||
<Form.Item name="age" label="Age (Not Watch)">
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
<Typography>
|
||||
<pre>Field 1 Value: {nameValue}</pre>
|
||||
<pre>Name Value: {nameValue}</pre>
|
||||
</Typography>
|
||||
</>
|
||||
);
|
||||
|
9
components/form/hooks/useFormInstance.ts
Normal file
9
components/form/hooks/useFormInstance.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { useContext } from 'react';
|
||||
import { FormContext } from '../context';
|
||||
import { FormInstance } from './useForm';
|
||||
|
||||
export default function useFormInstance<Value = any>(): FormInstance<Value> {
|
||||
const { form } = useContext(FormContext);
|
||||
|
||||
return form!;
|
||||
}
|
@ -279,6 +279,65 @@ validateFields()
|
||||
});
|
||||
```
|
||||
|
||||
## Hooks
|
||||
|
||||
### Form.useForm
|
||||
|
||||
`type Form.useForm = (): FormInstance`
|
||||
|
||||
Create Form instance to maintain data store.
|
||||
|
||||
### Form.useFormInstance
|
||||
|
||||
`type Form.useFormInstance = (): FormInstance`
|
||||
|
||||
Added in `4.20.0`. Get current context form instance to avoid pass as props between components:
|
||||
|
||||
```tsx
|
||||
const Sub = () => {
|
||||
const form = Form.useFormInstance();
|
||||
|
||||
return <Button onClick={() => form.setFieldsValue({})} />;
|
||||
};
|
||||
|
||||
export default () => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
return (
|
||||
<Form form={form}>
|
||||
<Sub />
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Form.useWatch
|
||||
|
||||
`type Form.useWatch = (namePath: NamePath, formInstance: FormInstance): Value`
|
||||
|
||||
Added in `4.20.0`. Watch the value of a field. You can use this to interactive with other hooks like `useSWR` to reduce develop cost:
|
||||
|
||||
```tsx
|
||||
const Demo = () => {
|
||||
const [form] = Form.useForm();
|
||||
const userName = Form.useWatch('username', form);
|
||||
|
||||
const { data: options } = useSWR(`/api/user/${userName}`, fetcher);
|
||||
|
||||
return (
|
||||
<Form form={form}>
|
||||
<Form.Item name="username">
|
||||
<AutoComplete options={options} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
#### Difference between other data fetching method
|
||||
|
||||
Form only update the Field which changed to avoid full refresh perf issue. Thus you can not get real time value with `getFieldsValue` in render. And `useWatch` will rerender current component to sync with latest value. You can also use Field renderProps to get better performance if only want to do conditional render. If component no need care field value change, you can use `onValuesChange` to give to parent component to avoid current one rerender.
|
||||
|
||||
### Interface
|
||||
|
||||
#### NamePath
|
||||
|
@ -5,11 +5,13 @@ import ErrorList, { ErrorListProps } from './ErrorList';
|
||||
import List, { FormListProps } from './FormList';
|
||||
import { FormProvider } from './context';
|
||||
import devWarning from '../_util/devWarning';
|
||||
import useFormInstance from './hooks/useFormInstance';
|
||||
|
||||
type InternalFormType = typeof InternalForm;
|
||||
|
||||
interface FormInterface extends InternalFormType {
|
||||
useForm: typeof useForm;
|
||||
useFormInstance: typeof useFormInstance;
|
||||
useWatch: typeof useWatch;
|
||||
Item: typeof Item;
|
||||
List: typeof List;
|
||||
@ -26,6 +28,7 @@ Form.Item = Item;
|
||||
Form.List = List;
|
||||
Form.ErrorList = ErrorList;
|
||||
Form.useForm = useForm;
|
||||
Form.useFormInstance = useFormInstance;
|
||||
Form.useWatch = useWatch;
|
||||
Form.Provider = FormProvider;
|
||||
Form.create = () => {
|
||||
|
@ -278,6 +278,65 @@ validateFields()
|
||||
});
|
||||
```
|
||||
|
||||
## Hooks
|
||||
|
||||
### Form.useForm
|
||||
|
||||
`type Form.useForm = (): FormInstance`
|
||||
|
||||
创建 Form 实例,用于管理所有数据状态。
|
||||
|
||||
### Form.useFormInstance
|
||||
|
||||
`type Form.useFormInstance = (): FormInstance`
|
||||
|
||||
`4.20.0` 新增,获取当前上下文正在使用的 Form 实例,常见于封装子组件消费无需透传 Form 实例:
|
||||
|
||||
```tsx
|
||||
const Sub = () => {
|
||||
const form = Form.useFormInstance();
|
||||
|
||||
return <Button onClick={() => form.setFieldsValue({})} />;
|
||||
};
|
||||
|
||||
export default () => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
return (
|
||||
<Form form={form}>
|
||||
<Sub />
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Form.useWatch
|
||||
|
||||
`type Form.useWatch = (namePath: NamePath, formInstance: FormInstance): Value`
|
||||
|
||||
`4.20.0` 新增,用于直接获取 form 中字段对应的值。通过该 Hooks 可以与诸如 `useSWR` 进行联动从而降低维护成本:
|
||||
|
||||
```tsx
|
||||
const Demo = () => {
|
||||
const [form] = Form.useForm();
|
||||
const userName = Form.useWatch('username', form);
|
||||
|
||||
const { data: options } = useSWR(`/api/user/${userName}`, fetcher);
|
||||
|
||||
return (
|
||||
<Form form={form}>
|
||||
<Form.Item name="username">
|
||||
<AutoComplete options={options} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
#### 与其他获取数据的方式的区别
|
||||
|
||||
Form 仅会对变更的 Field 进行刷新,从而避免完整的组件刷新可能引发的性能问题。因而你无法在 render 阶段通过 `form.getFieldsValue` 来实时获取字段值,而 `useWatch` 提供了一种特定字段访问的方式,从而使得在当前组件中可以直接消费字段的值。同时,如果为了更好的渲染性能,你可以通过 Field 的 renderProps 仅更新需要更新的部分。而当当前组件更新或者 effect 都不需要消费字段值时,则可以通过 `onValuesChange` 将数据抛出,从而避免组件更新。
|
||||
|
||||
### Interface
|
||||
|
||||
#### NamePath
|
||||
@ -439,7 +498,7 @@ React 中异步更新会导致受控组件交互行为异常。当用户交互
|
||||
|
||||
### `setFieldsValue` 不会触发 `onFieldsChange` 和 `onValuesChange`?
|
||||
|
||||
是的,change 事件仅当用户交互才会触发。该设计是为了防止在 change 事件中调用 `setFieldsValue` 导致的循环问题。
|
||||
是的,change 事件仅当用户交互才会触发。该设计是为了防止在 change 事件中调用 `setFieldsValue` 导致的循环问题。如果仅仅需要组件内消费,可以通过 `useWatch` 或者 `Field.renderProps` 来实现。
|
||||
|
||||
### 有更多参考文档吗?
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user