Skip to content

Commit 8bbfd34

Browse files
authored
feat: useWatch support selector (#637)
* feat: api * feat: test * feat: 代码优化 * feat: 添加显示声明类型支持 * feat: 下划线优化 * feat: 下划线优化 * feat: watchValue
1 parent 75dbcb3 commit 8bbfd34

File tree

4 files changed

+112
-8
lines changed

4 files changed

+112
-8
lines changed

docs/demo/useWatch-selector.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## useWatch-selector
2+
3+
<code src="../examples/useWatch-selector.tsx"></code>

docs/examples/useWatch-selector.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import React from 'react';
2+
import Form, { Field } from 'rc-field-form';
3+
import Input from './components/Input';
4+
5+
type FieldType = {
6+
init?: string;
7+
name?: string;
8+
age?: number;
9+
};
10+
11+
export default () => {
12+
const [form] = Form.useForm<FieldType>();
13+
const values = Form.useWatch(
14+
values => ({ init: values.init, newName: values.name, newAge: values.age }),
15+
{ form, preserve: true },
16+
);
17+
console.log('values', values);
18+
return (
19+
<>
20+
<Form form={form} initialValues={{ init: 'init', name: 'aaa' }}>
21+
name
22+
<Field name="name">
23+
<Input />
24+
</Field>
25+
age
26+
<Field name="age">
27+
<Input />
28+
</Field>
29+
no-watch
30+
<Field name="no-watch">
31+
<Input />
32+
</Field>
33+
values:{JSON.stringify(values)}
34+
</Form>
35+
</>
36+
);
37+
};

src/useWatch.ts

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,18 @@ function useWatch<TForm extends FormInstance>(
7676
form?: TForm | WatchOptions<TForm>,
7777
): GetGeneric<TForm>;
7878

79+
// ------- selector type -------
80+
function useWatch<TForm extends FormInstance, TSelected = unknown>(
81+
selector: (values: GetGeneric<TForm>) => TSelected,
82+
form?: TForm | WatchOptions<TForm>,
83+
): TSelected;
84+
85+
function useWatch<ValueType = Store, TSelected = unknown>(
86+
selector: (values: ValueType) => TSelected,
87+
form?: FormInstance | WatchOptions<FormInstance>,
88+
): TSelected;
89+
// ------- selector type end -------
90+
7991
function useWatch<TForm extends FormInstance>(
8092
dependencies: NamePath,
8193
form?: TForm | WatchOptions<TForm>,
@@ -86,8 +98,10 @@ function useWatch<ValueType = Store>(
8698
form?: FormInstance | WatchOptions<FormInstance>,
8799
): ValueType;
88100

89-
function useWatch(...args: [NamePath, FormInstance | WatchOptions<FormInstance>]) {
90-
const [dependencies = [], _form = {}] = args;
101+
function useWatch(
102+
...args: [NamePath | ((values: Store) => any), FormInstance | WatchOptions<FormInstance>]
103+
) {
104+
const [dependencies, _form = {}] = args;
91105
const options = isFormInstance(_form) ? { form: _form } : _form;
92106
const form = options.form;
93107

@@ -125,8 +139,15 @@ function useWatch(...args: [NamePath, FormInstance | WatchOptions<FormInstance>]
125139
const { getFieldsValue, getInternalHooks } = formInstance;
126140
const { registerWatch } = getInternalHooks(HOOK_MARK);
127141

142+
const getWatchValue = (values: any, allValues: any) => {
143+
const watchValue = options.preserve ? allValues : values;
144+
return typeof dependencies === 'function'
145+
? dependencies(watchValue)
146+
: getValue(watchValue, namePathRef.current);
147+
};
148+
128149
const cancelRegister = registerWatch((values, allValues) => {
129-
const newValue = getValue(options.preserve ? allValues : values, namePathRef.current);
150+
const newValue = getWatchValue(values, allValues);
130151
const nextValueStr = stringify(newValue);
131152

132153
// Compare stringify in case it's nest object
@@ -137,10 +158,7 @@ function useWatch(...args: [NamePath, FormInstance | WatchOptions<FormInstance>]
137158
});
138159

139160
// TODO: We can improve this perf in future
140-
const initialValue = getValue(
141-
options.preserve ? getFieldsValue(true) : getFieldsValue(),
142-
namePathRef.current,
143-
);
161+
const initialValue = getWatchValue(getFieldsValue(), getFieldsValue(true));
144162

145163
// React 18 has the bug that will queue update twice even the value is not changed
146164
// ref: https://github.com/facebook/react/issues/27213

tests/useWatch.test.tsx

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,8 +288,29 @@ describe('useWatch', () => {
288288
const demo5 = Form.useWatch(['demo1', 'demo2', 'demo3', 'demo4', 'demo5'], form);
289289
const more = Form.useWatch(['age', 'name', 'gender'], form);
290290
const demo = Form.useWatch<string>(['demo']);
291+
292+
const values2 = Form.useWatch(values => ({ newName: values.name, newAge: values.age }), form);
293+
const values3 = Form.useWatch<FieldType, { newName?: string }>(values => ({
294+
newName: values.name,
295+
}));
296+
291297
return (
292-
<>{JSON.stringify({ values, main, age, demo1, demo2, demo3, demo4, demo5, more, demo })}</>
298+
<>
299+
{JSON.stringify({
300+
values,
301+
main,
302+
age,
303+
demo1,
304+
demo2,
305+
demo3,
306+
demo4,
307+
demo5,
308+
more,
309+
demo,
310+
values2,
311+
values3,
312+
})}
313+
</>
293314
);
294315
};
295316

@@ -457,4 +478,29 @@ describe('useWatch', () => {
457478

458479
logSpy.mockRestore();
459480
});
481+
it('selector', async () => {
482+
const Demo: React.FC = () => {
483+
const [form] = Form.useForm<{ name?: string }>();
484+
const nameValue = Form.useWatch(values => values.name, form);
485+
return (
486+
<div>
487+
<Form form={form}>
488+
<Field name="name" initialValue="bamboo">
489+
<Input />
490+
</Field>
491+
</Form>
492+
<div className="values">{nameValue}</div>
493+
</div>
494+
);
495+
};
496+
497+
const { container } = render(<Demo />);
498+
await act(async () => {
499+
await timeout();
500+
});
501+
expect(container.querySelector<HTMLDivElement>('.values')?.textContent).toEqual('bamboo');
502+
const input = container.querySelectorAll<HTMLInputElement>('input');
503+
await changeValue(input[0], 'bamboo2');
504+
expect(container.querySelector<HTMLDivElement>('.values')?.textContent).toEqual('bamboo2');
505+
});
460506
});

0 commit comments

Comments
 (0)