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:
二货机器人 2022-04-15 15:51:09 +08:00 committed by GitHub
parent 9b73058045
commit beeaf2d221
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 334 additions and 40 deletions

View File

@ -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);

View File

@ -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>,
]

View File

@ -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>,
]

View File

@ -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);
});
});

View File

@ -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>({

View File

@ -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>
</>
);

View 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!;
}

View File

@ -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

View File

@ -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 = () => {

View File

@ -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` 来实现。
### 有更多参考文档吗?