From 8bbfd347b162f57877d2c4e7ed9b217526f46451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E6=9E=AB?= <7971419+crazyair@users.noreply.github.com> Date: Thu, 30 Nov 2023 11:27:48 +0800 Subject: [PATCH] feat: useWatch support selector (#637) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: api * feat: test * feat: 代码优化 * feat: 添加显示声明类型支持 * feat: 下划线优化 * feat: 下划线优化 * feat: watchValue --- docs/demo/useWatch-selector.md | 3 ++ docs/examples/useWatch-selector.tsx | 37 ++++++++++++++++++++++ src/useWatch.ts | 32 ++++++++++++++----- tests/useWatch.test.tsx | 48 ++++++++++++++++++++++++++++- 4 files changed, 112 insertions(+), 8 deletions(-) create mode 100644 docs/demo/useWatch-selector.md create mode 100644 docs/examples/useWatch-selector.tsx diff --git a/docs/demo/useWatch-selector.md b/docs/demo/useWatch-selector.md new file mode 100644 index 00000000..1c17ac8c --- /dev/null +++ b/docs/demo/useWatch-selector.md @@ -0,0 +1,3 @@ +## useWatch-selector + + diff --git a/docs/examples/useWatch-selector.tsx b/docs/examples/useWatch-selector.tsx new file mode 100644 index 00000000..7ecde689 --- /dev/null +++ b/docs/examples/useWatch-selector.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import Form, { Field } from 'rc-field-form'; +import Input from './components/Input'; + +type FieldType = { + init?: string; + name?: string; + age?: number; +}; + +export default () => { + const [form] = Form.useForm(); + const values = Form.useWatch( + values => ({ init: values.init, newName: values.name, newAge: values.age }), + { form, preserve: true }, + ); + console.log('values', values); + return ( + <> +
+ name + + + + age + + + + no-watch + + + + values:{JSON.stringify(values)} +
+ + ); +}; diff --git a/src/useWatch.ts b/src/useWatch.ts index e5144083..127c04d5 100644 --- a/src/useWatch.ts +++ b/src/useWatch.ts @@ -76,6 +76,18 @@ function useWatch( form?: TForm | WatchOptions, ): GetGeneric; +// ------- selector type ------- +function useWatch( + selector: (values: GetGeneric) => TSelected, + form?: TForm | WatchOptions, +): TSelected; + +function useWatch( + selector: (values: ValueType) => TSelected, + form?: FormInstance | WatchOptions, +): TSelected; +// ------- selector type end ------- + function useWatch( dependencies: NamePath, form?: TForm | WatchOptions, @@ -86,8 +98,10 @@ function useWatch( form?: FormInstance | WatchOptions, ): ValueType; -function useWatch(...args: [NamePath, FormInstance | WatchOptions]) { - const [dependencies = [], _form = {}] = args; +function useWatch( + ...args: [NamePath | ((values: Store) => any), FormInstance | WatchOptions] +) { + const [dependencies, _form = {}] = args; const options = isFormInstance(_form) ? { form: _form } : _form; const form = options.form; @@ -125,8 +139,15 @@ function useWatch(...args: [NamePath, FormInstance | WatchOptions] const { getFieldsValue, getInternalHooks } = formInstance; const { registerWatch } = getInternalHooks(HOOK_MARK); + const getWatchValue = (values: any, allValues: any) => { + const watchValue = options.preserve ? allValues : values; + return typeof dependencies === 'function' + ? dependencies(watchValue) + : getValue(watchValue, namePathRef.current); + }; + const cancelRegister = registerWatch((values, allValues) => { - const newValue = getValue(options.preserve ? allValues : values, namePathRef.current); + const newValue = getWatchValue(values, allValues); const nextValueStr = stringify(newValue); // Compare stringify in case it's nest object @@ -137,10 +158,7 @@ function useWatch(...args: [NamePath, FormInstance | WatchOptions] }); // TODO: We can improve this perf in future - const initialValue = getValue( - options.preserve ? getFieldsValue(true) : getFieldsValue(), - namePathRef.current, - ); + const initialValue = getWatchValue(getFieldsValue(), getFieldsValue(true)); // React 18 has the bug that will queue update twice even the value is not changed // ref: https://github.com/facebook/react/issues/27213 diff --git a/tests/useWatch.test.tsx b/tests/useWatch.test.tsx index 2c035975..843fe3a9 100644 --- a/tests/useWatch.test.tsx +++ b/tests/useWatch.test.tsx @@ -288,8 +288,29 @@ describe('useWatch', () => { const demo5 = Form.useWatch(['demo1', 'demo2', 'demo3', 'demo4', 'demo5'], form); const more = Form.useWatch(['age', 'name', 'gender'], form); const demo = Form.useWatch(['demo']); + + const values2 = Form.useWatch(values => ({ newName: values.name, newAge: values.age }), form); + const values3 = Form.useWatch(values => ({ + newName: values.name, + })); + return ( - <>{JSON.stringify({ values, main, age, demo1, demo2, demo3, demo4, demo5, more, demo })} + <> + {JSON.stringify({ + values, + main, + age, + demo1, + demo2, + demo3, + demo4, + demo5, + more, + demo, + values2, + values3, + })} + ); }; @@ -457,4 +478,29 @@ describe('useWatch', () => { logSpy.mockRestore(); }); + it('selector', async () => { + const Demo: React.FC = () => { + const [form] = Form.useForm<{ name?: string }>(); + const nameValue = Form.useWatch(values => values.name, form); + return ( +
+
+ + + +
+
{nameValue}
+
+ ); + }; + + const { container } = render(); + await act(async () => { + await timeout(); + }); + expect(container.querySelector('.values')?.textContent).toEqual('bamboo'); + const input = container.querySelectorAll('input'); + await changeValue(input[0], 'bamboo2'); + expect(container.querySelector('.values')?.textContent).toEqual('bamboo2'); + }); });