Skip to content

Commit 767aa17

Browse files
authored
feat: use deps as a shortcut of shouldUpdate when name is not set (#158)
* feat: use `deps` as a shortcut of `shouldUpdate` when name is not set * chore * refactor * refactor * refactor: remove redundant deps check * chore * chore * perf: remove useless rerender
1 parent 4182211 commit 767aa17

File tree

3 files changed

+123
-14
lines changed

3 files changed

+123
-14
lines changed

examples/deps.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React from 'react';
2+
import Form, { Field } from '../src';
3+
import Input from './components/Input';
4+
5+
let x = 0;
6+
7+
export default function Demo() {
8+
return (
9+
<Form>
10+
<Field dependencies={['field_1']}>
11+
{() => {
12+
x += 1;
13+
return `gogogo${x}`;
14+
}}
15+
</Field>
16+
<Field name="field_1">
17+
<Input />
18+
</Field>
19+
<Field name="field_2">
20+
<Input />
21+
</Field>
22+
</Form>
23+
);
24+
}

src/Field.tsx

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -257,29 +257,31 @@ class Field extends React.Component<InternalFieldProps, FieldState, InternalForm
257257
* Trigger when marked `dependencies` updated. Related fields will all update
258258
*/
259259
const dependencyList = dependencies.map(getNamePath);
260-
if (
261-
namePathMatch ||
262-
dependencyList.some(dependency => containsNamePath(info.relatedFields, dependency))
263-
) {
260+
// No need for `namePathMath` check and `shouldUpdate` check, since `valueUpdate` will be
261+
// emitted earlier and they will work there
262+
// If set it may cause unnecessary twice rerendering
263+
if (dependencyList.some(dependency => containsNamePath(info.relatedFields, dependency))) {
264264
this.reRender();
265265
return;
266266
}
267267
break;
268268
}
269269

270270
default:
271-
/**
272-
* - If `namePath` exists in `namePathList`, means it's related value and should update.
273-
* - If `dependencies` exists in `namePathList`, means upstream trigger update.
274-
* - If `shouldUpdate` provided, use customize logic to update the field
275-
* - else to check if value changed
276-
*/
271+
// 1. If `namePath` exists in `namePathList`, means it's related value and should update
272+
// For example <List name="list"><Field name={['list', 0]}></List>
273+
// If `namePathList` is [['list']] (List value update), Field should be updated
274+
// If `namePathList` is [['list', 0]] (Field value update), List shouldn't be updated
275+
// 2.
276+
// 2.1 If `dependencies` is set, `name` is not set and `shouldUpdate` is not set,
277+
// don't use `shouldUpdate`. `dependencies` is view as a shortcut if `shouldUpdate`
278+
// is not provided
279+
// 2.2 If `shouldUpdate` provided, use customize logic to update the field
280+
// else to check if value changed
277281
if (
278282
namePathMatch ||
279-
dependencies.some(dependency =>
280-
containsNamePath(namePathList, getNamePath(dependency)),
281-
) ||
282-
requireUpdate(shouldUpdate, prevStore, store, prevValue, curValue, info)
283+
((!dependencies.length || namePath.length || shouldUpdate) &&
284+
requireUpdate(shouldUpdate, prevStore, store, prevValue, curValue, info))
283285
) {
284286
this.reRender();
285287
return;

tests/dependencies.test.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,87 @@ describe('Form.Dependencies', () => {
122122
await changeValue(getField(wrapper, 1), 'light');
123123
matchError(getField(wrapper, 0), false);
124124
});
125+
126+
it('should work as a shortcut when name is not provided', async () => {
127+
const spy = jest.fn();
128+
const wrapper = mount(
129+
<Form>
130+
<Field dependencies={['field_1']}>
131+
{() => {
132+
spy();
133+
return 'gogogo';
134+
}}
135+
</Field>
136+
<Field name="field_1">
137+
<Input />
138+
</Field>
139+
<Field name="field_2">
140+
<Input />
141+
</Field>
142+
</Form>,
143+
);
144+
expect(spy).toHaveBeenCalledTimes(1);
145+
await changeValue(getField(wrapper, 2), 'value2');
146+
// sync start
147+
// valueUpdate -> not rerender
148+
// depsUpdate -> not rerender
149+
// sync end
150+
// async start
151+
// validateFinish -> not rerender
152+
// async end
153+
expect(spy).toHaveBeenCalledTimes(1);
154+
await changeValue(getField(wrapper, 1), 'value1');
155+
// sync start
156+
// valueUpdate -> not rerender
157+
// depsUpdate -> rerender by deps
158+
// [ react rerender once -> 2 ]
159+
// sync end
160+
// async start
161+
// validateFinish -> not rerender
162+
// async end
163+
expect(spy).toHaveBeenCalledTimes(2);
164+
});
165+
166+
it("shouldn't work when shouldUpdate is set", async () => {
167+
const spy = jest.fn();
168+
const wrapper = mount(
169+
<Form>
170+
<Field dependencies={['field_1']} shouldUpdate={() => true}>
171+
{() => {
172+
spy();
173+
return 'gogogo';
174+
}}
175+
</Field>
176+
<Field name="field_1">
177+
<Input />
178+
</Field>
179+
<Field name="field_2">
180+
<Input />
181+
</Field>
182+
</Form>,
183+
);
184+
expect(spy).toHaveBeenCalledTimes(1);
185+
await changeValue(getField(wrapper, 2), 'value2');
186+
// sync start
187+
// valueUpdate -> rerender by shouldUpdate
188+
// depsUpdate -> rerender by deps
189+
// [ react rerender once -> 2 ]
190+
// sync end
191+
// async start
192+
// validateFinish -> rerender by shouldUpdate
193+
// [ react rerender once -> 3 ]
194+
// async end
195+
expect(spy).toHaveBeenCalledTimes(3);
196+
await changeValue(getField(wrapper, 1), 'value1');
197+
// sync start
198+
// valueUpdate -> rerender by shouldUpdate
199+
// depsUpdate -> rerender by deps
200+
// [ react rerender once -> 4 ]
201+
// sync end
202+
// async start
203+
// validateFinish -> rerender by shouldUpdate
204+
// [ react rerender once -> 5 ]
205+
// async end
206+
expect(spy).toHaveBeenCalledTimes(5);
207+
});
125208
});

0 commit comments

Comments
 (0)